Add type validation for parameters and fix types

This commit introduces type validation for parameter values within the
parser/compiler, aligning with the YAML schema. It aims to eliminate
dependencies on side effects in the collection files.

This update changes the treatment of data types in the Windows
collection, moving away from unintended type casting by the compiler.
Previously, numeric and boolean values were used even though only
string types were supported. This behavior was unstable and untested,
and has now been adjusted to use strings exclusively.

Changes ensure that parameter values are correctly validated
as strings, enhancing stability and maintainability.
This commit is contained in:
undergroundwires
2024-06-19 17:01:27 +02:00
parent 48761f62a2
commit fac26a6ca0
43 changed files with 873 additions and 466 deletions

View File

@@ -1,9 +1,12 @@
import type { PropertyKeys } from '@/TypeHelpers';
import { isNullOrUndefined, isArray, isPlainObject } from '@/TypeHelpers';
import {
isNullOrUndefined, isArray, isPlainObject, isString,
} from '@/TypeHelpers';
export interface TypeValidator {
assertObject<T>(assertion: ObjectAssertion<T>): void;
assertNonEmptyCollection(assertion: NonEmptyCollectionAssertion): void;
assertNonEmptyString(assertion: NonEmptyStringAssertion): void;
}
export interface NonEmptyCollectionAssertion {
@@ -11,6 +14,17 @@ export interface NonEmptyCollectionAssertion {
readonly valueName: string;
}
export interface RegexValidationRule {
readonly expectedMatch: RegExp;
readonly errorMessage: string;
}
export interface NonEmptyStringAssertion {
readonly value: unknown;
readonly valueName: string;
readonly rule?: RegexValidationRule;
}
export interface ObjectAssertion<T> {
readonly value: T | unknown;
readonly valueName: string;
@@ -33,6 +47,18 @@ export function createTypeValidator(): TypeValidator {
assertArray(assertion.value, assertion.valueName);
assertNonEmpty(assertion.value, assertion.valueName);
},
assertNonEmptyString: (assertion) => {
assertDefined(assertion.value, assertion.valueName);
assertString(assertion.value, assertion.valueName);
if (assertion.value.length === 0) {
throw new Error(`'${assertion.valueName}' is missing.`);
}
if (assertion.rule) {
if (!assertion.value.match(assertion.rule.expectedMatch)) {
throw new Error(assertion.rule.errorMessage);
}
}
},
};
}
@@ -86,6 +112,15 @@ function assertArray(
}
}
function assertString(
value: unknown,
valueName: string,
): asserts value is string {
if (!isString(value)) {
throw new Error(`'${valueName}' should be of type 'string', but is of type '${typeof value}'.`);
}
}
function assertNonEmpty(
value: Array<unknown>,
valueName: string,

View File

@@ -4,7 +4,7 @@ import { createPositionFromRegexFullMatch, type ExpressionPositionFactory } from
import { createFunctionParameterCollection, type FunctionParameterCollectionFactory } from '../../../Function/Parameter/FunctionParameterCollectionFactory';
import type { IExpressionParser } from '../IExpressionParser';
import type { IExpression } from '../../Expression/IExpression';
import type { IFunctionParameter } from '../../../Function/Parameter/IFunctionParameter';
import type { FunctionParameter } from '../../../Function/Parameter/FunctionParameter';
import type { IFunctionParameterCollection, IReadOnlyFunctionParameterCollection } from '../../../Function/Parameter/IFunctionParameterCollection';
export interface RegexParserUtilities {
@@ -110,7 +110,7 @@ function createParameters(
export interface PrimitiveExpression {
readonly evaluator: ExpressionEvaluator;
readonly parameters?: readonly IFunctionParameter[];
readonly parameters?: readonly FunctionParameter[];
}
export interface ExpressionFactory {

View File

@@ -1,4 +1,3 @@
import { FunctionParameter } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameter';
import { RegexParser, type PrimitiveExpression } from '../Parser/Regex/RegexParser';
import { ExpressionRegexBuilder } from '../Parser/Regex/ExpressionRegexBuilder';
@@ -16,7 +15,10 @@ export class ParameterSubstitutionParser extends RegexParser {
const parameterName = match[1];
const pipeline = match[2];
return {
parameters: [new FunctionParameter(parameterName, false)],
parameters: [{
name: parameterName,
isOptional: false,
}],
evaluator: (context) => {
const { argumentValue } = context.args.getArgument(parameterName);
if (!pipeline) {

View File

@@ -1,7 +1,6 @@
// eslint-disable-next-line max-classes-per-file
import type { IExpressionParser } from '@/application/Parser/Executable/Script/Compiler/Expressions/Parser/IExpressionParser';
import { FunctionParameterCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterCollection';
import { FunctionParameter } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameter';
import { ExpressionPosition } from '../Expression/ExpressionPosition';
import { ExpressionRegexBuilder } from '../Parser/Regex/ExpressionRegexBuilder';
import { createPositionFromRegexFullMatch } from '../Expression/ExpressionPositionFactory';
@@ -84,7 +83,10 @@ class WithStatementBuilder {
public buildExpression(endExpressionPosition: ExpressionPosition, input: string): IExpression {
const parameters = new FunctionParameterCollection();
parameters.addParameter(new FunctionParameter(this.parameterName, true));
parameters.addParameter({
name: this.parameterName,
isOptional: true,
});
const position = new ExpressionPosition(
this.startExpressionPosition.start,
endExpressionPosition.end,

View File

@@ -1,14 +1,41 @@
import { ensureValidParameterName } from '../../Shared/ParameterNameValidator';
import type { IFunctionCallArgument } from './IFunctionCallArgument';
import { createTypeValidator, type TypeValidator } from '@/application/Parser/Common/TypeValidator';
import { validateParameterName, type ParameterNameValidator } from '@/application/Parser/Executable/Script/Compiler/Function/Shared/ParameterNameValidator';
export class FunctionCallArgument implements IFunctionCallArgument {
constructor(
public readonly parameterName: string,
public readonly argumentValue: string,
) {
ensureValidParameterName(parameterName);
if (!argumentValue) {
throw new Error(`Missing argument value for the parameter "${parameterName}".`);
export interface FunctionCallArgument {
readonly parameterName: string;
readonly argumentValue: string;
}
export interface FunctionCallArgumentFactory {
(
parameterName: string,
argumentValue: string,
utilities?: FunctionCallArgumentFactoryUtilities,
): FunctionCallArgument;
}
export const createFunctionCallArgument: FunctionCallArgumentFactory = (
parameterName: string,
argumentValue: string,
utilities: FunctionCallArgumentFactoryUtilities = DefaultUtilities,
): FunctionCallArgument => {
utilities.validateParameterName(parameterName);
utilities.typeValidator.assertNonEmptyString({
value: argumentValue,
valueName: `Missing argument value for the parameter "${parameterName}".`,
});
return {
parameterName,
argumentValue,
};
};
interface FunctionCallArgumentFactoryUtilities {
readonly typeValidator: TypeValidator;
readonly validateParameterName: ParameterNameValidator;
}
const DefaultUtilities: FunctionCallArgumentFactoryUtilities = {
typeValidator: createTypeValidator(),
validateParameterName,
};

View File

@@ -1,10 +1,10 @@
import type { IFunctionCallArgument } from './IFunctionCallArgument';
import type { FunctionCallArgument } from './FunctionCallArgument';
import type { IFunctionCallArgumentCollection } from './IFunctionCallArgumentCollection';
export class FunctionCallArgumentCollection implements IFunctionCallArgumentCollection {
private readonly arguments = new Map<string, IFunctionCallArgument>();
private readonly arguments = new Map<string, FunctionCallArgument>();
public addArgument(argument: IFunctionCallArgument): void {
public addArgument(argument: FunctionCallArgument): void {
if (this.hasArgument(argument.parameterName)) {
throw new Error(`argument value for parameter ${argument.parameterName} is already provided`);
}
@@ -22,7 +22,7 @@ export class FunctionCallArgumentCollection implements IFunctionCallArgumentColl
return this.arguments.has(parameterName);
}
public getArgument(parameterName: string): IFunctionCallArgument {
public getArgument(parameterName: string): FunctionCallArgument {
if (!parameterName) {
throw new Error('missing parameter name');
}

View File

@@ -1,4 +0,0 @@
export interface IFunctionCallArgument {
readonly parameterName: string;
readonly argumentValue: string;
}

View File

@@ -1,11 +1,11 @@
import type { IFunctionCallArgument } from './IFunctionCallArgument';
import type { FunctionCallArgument } from './FunctionCallArgument';
export interface IReadOnlyFunctionCallArgumentCollection {
getArgument(parameterName: string): IFunctionCallArgument;
getArgument(parameterName: string): FunctionCallArgument;
getAllParameterNames(): string[];
hasArgument(parameterName: string): boolean;
}
export interface IFunctionCallArgumentCollection extends IReadOnlyFunctionCallArgumentCollection {
addArgument(argument: IFunctionCallArgument): void;
addArgument(argument: FunctionCallArgument): void;
}

View File

@@ -1,5 +1,4 @@
import type { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
import { FunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
import { FunctionCallArgumentCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
import { ExpressionsCompiler } from '@/application/Parser/Executable/Script/Compiler/Expressions/ExpressionsCompiler';
import type { IExpressionsCompiler } from '@/application/Parser/Executable/Script/Compiler/Expressions/IExpressionsCompiler';
@@ -7,14 +6,11 @@ import type { FunctionCall } from '@/application/Parser/Executable/Script/Compil
import type { FunctionCallCompilationContext } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
import { ParsedFunctionCall } from '@/application/Parser/Executable/Script/Compiler/Function/Call/ParsedFunctionCall';
import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/Common/ContextualError';
import { createFunctionCallArgument, type FunctionCallArgument, type FunctionCallArgumentFactory } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
import type { ArgumentCompiler } from './ArgumentCompiler';
export class NestedFunctionArgumentCompiler implements ArgumentCompiler {
constructor(
private readonly expressionsCompiler: IExpressionsCompiler = new ExpressionsCompiler(),
private readonly wrapError: ErrorWithContextWrapper
= wrapErrorWithAdditionalContext,
) { }
constructor(private readonly utilities: ArgumentCompilationUtilities = DefaultUtilities) { }
public createCompiledNestedCall(
nestedFunction: FunctionCall,
@@ -25,10 +21,7 @@ export class NestedFunctionArgumentCompiler implements ArgumentCompiler {
nestedFunction,
parentFunction.args,
context,
{
expressionsCompiler: this.expressionsCompiler,
wrapError: this.wrapError,
},
this.utilities,
);
const compiledCall = new ParsedFunctionCall(nestedFunction.functionName, compiledArgs);
return compiledCall;
@@ -38,6 +31,7 @@ export class NestedFunctionArgumentCompiler implements ArgumentCompiler {
interface ArgumentCompilationUtilities {
readonly expressionsCompiler: IExpressionsCompiler,
readonly wrapError: ErrorWithContextWrapper;
readonly createCallArgument: FunctionCallArgumentFactory;
}
function compileNestedFunctionArguments(
@@ -78,7 +72,7 @@ function compileNestedFunctionArguments(
.map(({
parameterName,
compiledArgumentValue,
}) => new FunctionCallArgument(parameterName, compiledArgumentValue));
}) => utilities.createCallArgument(parameterName, compiledArgumentValue));
return buildArgumentCollectionFromArguments(compiledArguments);
}
@@ -118,3 +112,9 @@ function buildArgumentCollectionFromArguments(
return compiledArgs;
}, new FunctionCallArgumentCollection());
}
const DefaultUtilities: ArgumentCompilationUtilities = {
expressionsCompiler: new ExpressionsCompiler(),
wrapError: wrapErrorWithAdditionalContext,
createCallArgument: createFunctionCallArgument,
};

View File

@@ -1,24 +1,38 @@
import type { FunctionCallData, FunctionCallsData, FunctionCallParametersData } from '@/application/collections/';
import type {
FunctionCallData,
FunctionCallsData,
FunctionCallParametersData,
} from '@/application/collections/';
import { isArray, isPlainObject } from '@/TypeHelpers';
import { createTypeValidator, type TypeValidator } from '@/application/Parser/Common/TypeValidator';
import { FunctionCallArgumentCollection } from './Argument/FunctionCallArgumentCollection';
import { FunctionCallArgument } from './Argument/FunctionCallArgument';
import { ParsedFunctionCall } from './ParsedFunctionCall';
import { createFunctionCallArgument, type FunctionCallArgumentFactory } from './Argument/FunctionCallArgument';
import type { FunctionCall } from './FunctionCall';
export interface FunctionCallsParser {
(
calls: FunctionCallsData,
validator?: TypeValidator,
utilities?: FunctionCallParsingUtilities,
): FunctionCall[];
}
interface FunctionCallParsingUtilities {
readonly typeValidator: TypeValidator;
readonly createCallArgument: FunctionCallArgumentFactory;
}
const DefaultUtilities: FunctionCallParsingUtilities = {
typeValidator: createTypeValidator(),
createCallArgument: createFunctionCallArgument,
};
export const parseFunctionCalls: FunctionCallsParser = (
calls,
validator = createTypeValidator(),
utilities = DefaultUtilities,
) => {
const sequence = getCallSequence(calls, validator);
return sequence.map((call) => parseFunctionCall(call, validator));
const sequence = getCallSequence(calls, utilities.typeValidator);
return sequence.map((call) => parseFunctionCall(call, utilities));
};
function getCallSequence(calls: FunctionCallsData, validator: TypeValidator): FunctionCallData[] {
@@ -38,23 +52,27 @@ function getCallSequence(calls: FunctionCallsData, validator: TypeValidator): Fu
function parseFunctionCall(
call: FunctionCallData,
validator: TypeValidator,
utilities: FunctionCallParsingUtilities,
): FunctionCall {
validator.assertObject({
utilities.typeValidator.assertObject({
value: call,
valueName: 'function call',
allowedProperties: ['function', 'parameters'],
});
const callArgs = parseArgs(call.parameters);
const callArgs = parseArgs(call.parameters, utilities.createCallArgument);
return new ParsedFunctionCall(call.function, callArgs);
}
function parseArgs(
parameters: FunctionCallParametersData | undefined,
createArgument: FunctionCallArgumentFactory,
): FunctionCallArgumentCollection {
const parametersMap = parameters ?? {};
return Object.keys(parametersMap)
.map((parameterName) => new FunctionCallArgument(parameterName, parametersMap[parameterName]))
.map((parameterName) => {
const argumentValue = parametersMap[parameterName];
return createArgument(parameterName, argumentValue);
})
.reduce((args, arg) => {
args.addArgument(arg);
return args;

View File

@@ -1,11 +1,4 @@
import { ensureValidParameterName } from '../Shared/ParameterNameValidator';
import type { IFunctionParameter } from './IFunctionParameter';
export class FunctionParameter implements IFunctionParameter {
constructor(
public readonly name: string,
public readonly isOptional: boolean,
) {
ensureValidParameterName(name);
}
export interface FunctionParameter {
readonly name: string;
readonly isOptional: boolean;
}

View File

@@ -1,14 +1,14 @@
import type { IFunctionParameterCollection } from './IFunctionParameterCollection';
import type { IFunctionParameter } from './IFunctionParameter';
import type { FunctionParameter } from './FunctionParameter';
export class FunctionParameterCollection implements IFunctionParameterCollection {
private parameters = new Array<IFunctionParameter>();
private parameters = new Array<FunctionParameter>();
public get all(): readonly IFunctionParameter[] {
public get all(): readonly FunctionParameter[] {
return this.parameters;
}
public addParameter(parameter: IFunctionParameter) {
public addParameter(parameter: FunctionParameter) {
this.ensureValidParameter(parameter);
this.parameters.push(parameter);
}
@@ -17,7 +17,7 @@ export class FunctionParameterCollection implements IFunctionParameterCollection
return this.parameters.find((existingParameter) => existingParameter.name === name);
}
private ensureValidParameter(parameter: IFunctionParameter) {
private ensureValidParameter(parameter: FunctionParameter) {
if (this.includesName(parameter.name)) {
throw new Error(`duplicate parameter name: "${parameter.name}"`);
}

View File

@@ -0,0 +1,21 @@
import type { ParameterDefinitionData } from '@/application/collections/';
import { validateParameterName, type ParameterNameValidator } from '../Shared/ParameterNameValidator';
import type { FunctionParameter } from './FunctionParameter';
export interface FunctionParameterParser {
(
data: ParameterDefinitionData,
validator?: ParameterNameValidator,
): FunctionParameter;
}
export const parseFunctionParameter: FunctionParameterParser = (
data,
validator = validateParameterName,
) => {
validator(data.name);
return {
name: data.name,
isOptional: data.optional || false,
};
};

View File

@@ -1,4 +0,0 @@
export interface IFunctionParameter {
readonly name: string;
readonly isOptional: boolean;
}

View File

@@ -1,9 +1,9 @@
import type { IFunctionParameter } from './IFunctionParameter';
import type { FunctionParameter } from './FunctionParameter';
export interface IReadOnlyFunctionParameterCollection {
readonly all: readonly IFunctionParameter[];
readonly all: readonly FunctionParameter[];
}
export interface IFunctionParameterCollection extends IReadOnlyFunctionParameterCollection {
addParameter(parameter: IFunctionParameter): void;
addParameter(parameter: FunctionParameter): void;
}

View File

@@ -1,8 +1,22 @@
export function ensureValidParameterName(parameterName: string) {
if (!parameterName) {
throw new Error('missing parameter name');
}
if (!parameterName.match(/^[0-9a-zA-Z]+$/)) {
throw new Error(`parameter name must be alphanumeric but it was "${parameterName}"`);
}
import { createTypeValidator, type TypeValidator } from '@/application/Parser/Common/TypeValidator';
export interface ParameterNameValidator {
(
parameterName: string,
typeValidator?: TypeValidator,
): void;
}
export const validateParameterName = (
parameterName: string,
typeValidator = createTypeValidator(),
) => {
typeValidator.assertNonEmptyString({
value: parameterName,
valueName: 'parameter name',
rule: {
expectedMatch: /^[0-9a-zA-Z]+$/,
errorMessage: `parameter name must be alphanumeric but it was "${parameterName}".`,
},
});
};

View File

@@ -11,9 +11,10 @@ import { isArray, isNullOrUndefined, isPlainObject } from '@/TypeHelpers';
import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/Common/ContextualError';
import { createFunctionWithInlineCode, createCallerFunction } from './SharedFunction';
import { SharedFunctionCollection } from './SharedFunctionCollection';
import { FunctionParameter } from './Parameter/FunctionParameter';
import { parseFunctionCalls, type FunctionCallsParser } from './Call/FunctionCallsParser';
import { createFunctionParameterCollection, type FunctionParameterCollectionFactory } from './Parameter/FunctionParameterCollectionFactory';
import { parseFunctionParameter, type FunctionParameterParser } from './Parameter/FunctionParameterParser';
import type { FunctionParameter } from './Parameter/FunctionParameter';
import type { ISharedFunctionCollection } from './ISharedFunctionCollection';
import type { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
import type { ISharedFunction } from './ISharedFunction';
@@ -46,7 +47,7 @@ export const parseSharedFunctions: SharedFunctionsParser = (
const DefaultUtilities: SharedFunctionsParsingUtilities = {
wrapError: wrapErrorWithAdditionalContext,
createParameter: (...args) => new FunctionParameter(...args),
parseParameter: parseFunctionParameter,
codeValidator: CodeValidator.instance,
createParameterCollection: createFunctionParameterCollection,
parseFunctionCalls,
@@ -54,16 +55,12 @@ const DefaultUtilities: SharedFunctionsParsingUtilities = {
interface SharedFunctionsParsingUtilities {
readonly wrapError: ErrorWithContextWrapper;
readonly createParameter: FunctionParameterFactory;
readonly parseParameter: FunctionParameterParser;
readonly codeValidator: ICodeValidator;
readonly createParameterCollection: FunctionParameterCollectionFactory;
readonly parseFunctionCalls: FunctionCallsParser;
}
export type FunctionParameterFactory = (
...args: ConstructorParameters<typeof FunctionParameter>
) => FunctionParameter;
function parseFunction(
data: FunctionData,
syntax: ILanguageSyntax,
@@ -100,7 +97,7 @@ function parseParameters(
utilities: SharedFunctionsParsingUtilities,
): IReadOnlyFunctionParameterCollection {
return (data.parameters || [])
.map((parameter) => createFunctionParameter(
.map((parameter) => parseParameterWithContextualError(
data.name,
parameter,
utilities,
@@ -111,16 +108,13 @@ function parseParameters(
}, utilities.createParameterCollection());
}
function createFunctionParameter(
function parseParameterWithContextualError(
functionName: string,
parameterData: ParameterDefinitionData,
utilities: SharedFunctionsParsingUtilities,
): FunctionParameter {
try {
return utilities.createParameter(
parameterData.name,
parameterData.optional || false,
);
return utilities.parseParameter(parameterData);
} catch (err) {
throw utilities.wrapError(
err,

View File

@@ -4,30 +4,32 @@ import { CompositeExpressionParser } from '@/application/Parser/Executable/Scrip
import { ExpressionsCompiler } from '@/application/Parser/Executable/Script/Compiler/Expressions/ExpressionsCompiler';
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
import { FunctionCallArgumentCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
import { FunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
import type { IExpressionParser } from '@/application/Parser/Executable/Script/Compiler/Expressions/Parser/IExpressionParser';
import type { ICodeSubstituter } from './ICodeSubstituter';
export class CodeSubstituter implements ICodeSubstituter {
constructor(
private readonly compiler: IExpressionsCompiler = createSubstituteCompiler(),
private readonly date = new Date(),
) {
import { createFunctionCallArgument, type FunctionCallArgumentFactory } from '../Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
export interface CodeSubstituter {
(
code: string,
projectDetails: ProjectDetails,
utilities?: CodeSubstitutionUtilities,
): string;
}
public substitute(code: string, projectDetails: ProjectDetails): string {
export const substituteCode: CodeSubstituter = (
code,
projectDetails,
utilities = DefaultUtilities,
) => {
if (!code) { throw new Error('missing code'); }
const args = new FunctionCallArgumentCollection();
const substitute = (name: string, value: string) => args
.addArgument(new FunctionCallArgument(name, value));
.addArgument(utilities.createCallArgument(name, value));
substitute('homepage', projectDetails.homepage);
substitute('version', projectDetails.version.toString());
substitute('date', this.date.toUTCString());
const compiledCode = this.compiler.compileExpressions(code, args);
substitute('date', utilities.provideDate().toUTCString());
const compiledCode = utilities.compiler.compileExpressions(code, args);
return compiledCode;
}
}
};
function createSubstituteCompiler(): IExpressionsCompiler {
const parsers: readonly IExpressionParser[] = [
@@ -37,3 +39,15 @@ function createSubstituteCompiler(): IExpressionsCompiler {
const expressionCompiler = new ExpressionsCompiler(parser);
return expressionCompiler;
}
interface CodeSubstitutionUtilities {
readonly compiler: IExpressionsCompiler;
readonly provideDate: () => Date;
readonly createCallArgument: FunctionCallArgumentFactory;
}
const DefaultUtilities: CodeSubstitutionUtilities = {
compiler: createSubstituteCompiler(),
provideDate: () => new Date(),
createCallArgument: createFunctionCallArgument,
};

View File

@@ -1,5 +0,0 @@
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
export interface ICodeSubstituter {
substitute(code: string, projectDetails: ProjectDetails): string;
}

View File

@@ -5,8 +5,7 @@ import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
import { createEnumParser, type EnumParser } from '../../Common/Enum';
import { createTypeValidator, type TypeValidator } from '../Common/TypeValidator';
import { CodeSubstituter } from './CodeSubstituter';
import type { ICodeSubstituter } from './ICodeSubstituter';
import { type CodeSubstituter, substituteCode } from './CodeSubstituter';
export const parseScriptingDefinition: ScriptingDefinitionParser = (
definition,
@@ -15,8 +14,8 @@ export const parseScriptingDefinition: ScriptingDefinitionParser = (
) => {
validateData(definition, utilities.validator);
const language = utilities.languageParser.parseEnum(definition.language, 'language');
const startCode = utilities.codeSubstituter.substitute(definition.startCode, projectDetails);
const endCode = utilities.codeSubstituter.substitute(definition.endCode, projectDetails);
const startCode = utilities.codeSubstituter(definition.startCode, projectDetails);
const endCode = utilities.codeSubstituter(definition.endCode, projectDetails);
return new ScriptingDefinition(
language,
startCode,
@@ -45,12 +44,12 @@ function validateData(
interface ScriptingDefinitionParserUtilities {
readonly languageParser: EnumParser<ScriptingLanguage>;
readonly codeSubstituter: ICodeSubstituter;
readonly codeSubstituter: CodeSubstituter;
readonly validator: TypeValidator;
}
const DefaultUtilities: ScriptingDefinitionParserUtilities = {
languageParser: createEnumParser(ScriptingLanguage),
codeSubstituter: new CodeSubstituter(),
codeSubstituter: substituteCode,
validator: createTypeValidator(),
};

View File

@@ -552,7 +552,7 @@ actions:
function: ClearDirectoryContents
parameters:
directoryGlob: '%USERPROFILE%\Local Settings\Temporary Internet Files'
grantPermissions: true # 🔒️ Protected on Windows 10 since 22H2 | 📂 Unprotected on Windows 11 since 22H2
grantPermissions: 'true' # 🔒️ Protected on Windows 10 since 22H2 | 📂 Unprotected on Windows 11 since 22H2
-
function: ClearDirectoryContents
parameters:
@@ -564,7 +564,7 @@ actions:
# - C:\Users\undergroundwires\AppData\Local\Microsoft\Windows\Temporary Internet Files\Virtualized
# Since Windows 10 22H2 and Windows 11 22H2, data files are observed in this subdirectories but not on the parent.
# Especially in `IE` folder includes many files. These folders are protected and hidden by default.
grantPermissions: true # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 22H2
grantPermissions: 'true' # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 22H2
-
function: ClearDirectoryContents
parameters:
@@ -573,7 +573,7 @@ actions:
function: ClearDirectoryContents
parameters:
directoryGlob: '%LOCALAPPDATA%\Temporary Internet Files'
grantPermissions: true # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 22H2
grantPermissions: 'true' # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 22H2
-
name: Clear Internet Explorer feeds cache
recommend: standard
@@ -1217,7 +1217,7 @@ actions:
function: StopService
parameters:
serviceName: wuauserv
waitUntilStopped: true
waitUntilStopped: 'true'
serviceRestartStateFile: '%APPDATA%\privacy.sexy-wuauserv' # Marked: refactor-with-variables (app dir should be unified, not using %TEMP% as it can be cleaned during operation)
-
function: ClearDirectoryContents
@@ -1303,18 +1303,18 @@ actions:
function: StopService
parameters:
serviceName: DiagTrack
waitUntilStopped: true
waitUntilStopped: 'true'
serviceRestartStateFile: '%APPDATA%\privacy.sexy-DiagTrack' # Marked: refactor-with-variables (app dir should be unified, not using %TEMP% as it can be cleaned during operation)
-
function: DeleteFiles
parameters:
fileGlob: '%PROGRAMDATA%\Microsoft\Diagnosis\ETLLogs\AutoLogger\AutoLogger-Diagtrack-Listener.etl'
grantPermissions: true
grantPermissions: 'true'
-
function: DeleteFiles
parameters:
fileGlob: '%PROGRAMDATA%\Microsoft\Diagnosis\ETLLogs\ShutdownLogger\AutoLogger-Diagtrack-Listener.etl'
grantPermissions: true
grantPermissions: 'true'
-
function: StartService
parameters:
@@ -1371,7 +1371,7 @@ actions:
function: ClearDirectoryContents # Otherwise it cannot access/delete files under `Scans\History`, see https://github.com/undergroundwires/privacy.sexy/issues/246
parameters:
directoryGlob: '%ProgramData%\Microsoft\Windows Defender\Scans\History'
grantPermissions: true # Running as TrustedInstaller is not needed, and causes Defender to alarm https://github.com/undergroundwires/privacy.sexy/issues/264
grantPermissions: 'true' # Running as TrustedInstaller is not needed, and causes Defender to alarm https://github.com/undergroundwires/privacy.sexy/issues/264
-
name: Clear credentials in Windows Credential Manager
call:
@@ -1508,13 +1508,13 @@ actions:
function: StopService
parameters:
serviceName: DPS
waitUntilStopped: true
waitUntilStopped: 'true'
serviceRestartStateFile: '%APPDATA%\privacy.sexy-DPS' # Marked: refactor-with-variables (app dir should be unified, not using %TEMP% as it can be cleaned during operation)
-
function: DeleteFiles
parameters:
fileGlob: '%WINDIR%\System32\sru\SRUDB.dat'
grantPermissions: true
grantPermissions: 'true'
-
function: StartService
parameters:
@@ -1526,7 +1526,7 @@ actions:
function: DeleteDirectory
parameters:
directoryGlob: '%SYSTEMDRIVE%\Windows.old'
grantPermissions: true
grantPermissions: 'true'
-
category: Disable OS data collection
children:
@@ -2813,7 +2813,7 @@ actions:
function: SoftDeleteFiles
parameters:
fileGlob: '%WINDIR%\System32\CompatTelRunner.exe'
grantPermissions: true # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 23H2
grantPermissions: 'true' # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 23H2
-
category: Disable background application compatibility checks (Application Experience scheduled tasks)
docs: |-
@@ -3037,7 +3037,7 @@ actions:
# Check: Get-ScheduledTask -TaskPath '\Microsoft\Windows\Application Experience\' -TaskName 'SdbinstMergeDbTask'
taskPathPattern: \Microsoft\Windows\Application Experience\
taskNamePattern: SdbinstMergeDbTask
grantPermissions: true # 🔒 No permissions, tested since [≥ Windows 11 23H2]
grantPermissions: 'true' # 🔒 No permissions, tested since [≥ Windows 11 23H2]
-
name: Disable application backup data gathering (`MareBackup`)
recommend: strict
@@ -5985,12 +5985,12 @@ actions:
function: SoftDeleteFiles
parameters:
fileGlob: '%PROGRAMFILES(X86)%\NVIDIA Corporation\NvTelemetry\*'
recurse: true
recurse: 'true'
-
function: SoftDeleteFiles
parameters:
fileGlob: '%PROGRAMFILES%\NVIDIA Corporation\NvTelemetry\*'
recurse: true
recurse: 'true'
-
name: Disable Nvidia telemetry drivers
recommend: standard
@@ -5998,7 +5998,7 @@ actions:
function: SoftDeleteFiles
parameters:
fileGlob: '%SYSTEMROOT%\System32\DriverStore\FileRepository\NvTelemetry*.dll'
recurse: true
recurse: 'true'
-
name: Disable participation in Nvidia telemetry
recommend: standard
@@ -7047,7 +7047,7 @@ actions:
parameters:
serviceName: adobeupdateservice # Check: (Get-Service -Name adobeupdateservice).StartType
defaultStartupMode: Automatic # Allowed values: Automatic | Manual
ignoreMissingOnRevert: true
ignoreMissingOnRevert: 'true'
-
name: Disable "Adobe Acrobat Update Task" scheduled task
recommend: standard
@@ -7091,7 +7091,7 @@ actions:
parameters:
serviceName: Razer Game Scanner Service # Check: (Get-Service -Name 'Razer Game Scanner Service').StartType
defaultStartupMode: Manual # Allowed values: Automatic | Manual
ignoreMissingOnRevert: true
ignoreMissingOnRevert: 'true'
-
name: Disable "Logitech Gaming Registry Service"
recommend: standard
@@ -7415,7 +7415,7 @@ actions:
function: RequireTLSMinimumKeySize
parameters:
algorithmName: Diffie-Hellman
keySizeInBits: 2048
keySizeInBits: '2048'
-
name: Enable strong RSA key requirement (breaks Hyper-V VMs)
recommend: strict # Microsoft deprecated it and will end support; but breaks Hyper-V VMs, see #363
@@ -7467,8 +7467,8 @@ actions:
function: RequireTLSMinimumKeySize
parameters:
algorithmName: PKCS
keySizeInBits: 2048
ignoreServerSide: true # Controlled by the specified server certificate
keySizeInBits: '2048'
ignoreServerSide: 'true' # Controlled by the specified server certificate
-
category: Disable insecure connections
docs: |- # refactor-with-variables: Same • Caution
@@ -8140,23 +8140,23 @@ actions:
function: DisableWindowsFeature
parameters:
featureName: SMB1Protocol # Get-WindowsOptionalFeature -FeatureName 'SMB1Protocol' -Online
disabledByDefault: true
disabledByDefault: 'true'
-
function: DisableWindowsFeature
parameters:
featureName: SMB1Protocol-Client # Get-WindowsOptionalFeature -FeatureName 'SMB1Protocol-Client' -Online
disabledByDefault: true
disabledByDefault: 'true'
-
function: DisableWindowsFeature
parameters:
featureName: SMB1Protocol-Server # Get-WindowsOptionalFeature -FeatureName 'SMB1Protocol-Server' -Online
disabledByDefault: true
disabledByDefault: 'true'
-
function: DisableService
parameters:
serviceName: mrxsmb10 # Check: (Get-Service -Name 'mrxsmb10').StartType
defaultStartupMode: Automatic # Allowed values: Boot | System | Automatic | Manual
ignoreMissingOnRevert: true # This service is only available when SMB1 feature is installed
ignoreMissingOnRevert: 'true' # This service is only available when SMB1 feature is installed
-
function: RunInlineCode
# This ensures that `lanmanworkstation` does not depend on `mrxsmb10` to avoid potential system issues.
@@ -8809,7 +8809,7 @@ actions:
function: DisableWindowsFeature
parameters:
featureName: TelnetClient # Get-WindowsOptionalFeature -FeatureName 'TelnetClient' -Online
disabledByDefault: true
disabledByDefault: 'true'
-
name: Remove "RAS Connection Manager Administration Kit (CMAK)" capability
docs: |- # refactor-with-variables: Same • Caution
@@ -9005,7 +9005,7 @@ actions:
function: DisableWindowsFeature
parameters:
featureName: TFTP # Get-WindowsOptionalFeature -FeatureName 'TFTP' -Online
disabledByDefault: true
disabledByDefault: 'true'
-
name: Remove "RIP Listener" capability
docs: |- # refactor-with-variables: *Caution**
@@ -10456,7 +10456,7 @@ actions:
function: SoftDeleteFiles
parameters:
fileGlob: '%SYSTEMROOT%\System32\drivers\mpsdrv.sys'
grantPermissions: true # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 22H2
grantPermissions: 'true' # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 22H2
-
function: ShowComputerRestartSuggestion
-
@@ -10527,7 +10527,7 @@ actions:
function: SoftDeleteFiles
parameters:
fileGlob: '%WINDIR%\System32\mpssvc.dll'
grantPermissions: true # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 22H2
grantPermissions: 'true' # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 22H2
-
function: ShowComputerRestartSuggestion
-
@@ -10783,7 +10783,7 @@ actions:
property: SubmitSamplesConsent # Status: Get-MpPreference | Select-Object -Property SubmitSamplesConsent
value: "'2'" # Set: Set-MpPreference -Force -SubmitSamplesConsent 2
default: "'1'" # Default: 1 (Send safe samples automatically) | Remove-MpPreference -Force -SubmitSamplesConsent | Set-MpPreference -Force -SubmitSamplesConsent 1
setDefaultOnWindows11: true # `Remove-MpPreference` sets it to 0 instead 1 (OS default) in Windows 11
setDefaultOnWindows11: 'true' # `Remove-MpPreference` sets it to 0 instead 1 (OS default) in Windows 11
-
function: SetRegistryValue
parameters:
@@ -11276,7 +11276,7 @@ actions:
property: QuarantinePurgeItemsAfterDelay # Status: Get-MpPreference | Select-Object -Property QuarantinePurgeItemsAfterDelay
value: "'1'" # Set: Set-MpPreference -Force -QuarantinePurgeItemsAfterDelay 1
default: "'90'" # Default: 90 | Remove-MpPreference -Force -QuarantinePurgeItemsAfterDelay | Set-MpPreference -Force -QuarantinePurgeItemsAfterDelay 90
setDefaultOnWindows11: true # `Remove-MpPreference` sets it to 0 instead 90 (OS default) in Windows 11
setDefaultOnWindows11: 'true' # `Remove-MpPreference` sets it to 0 instead 90 (OS default) in Windows 11
-
function: SetRegistryValue
parameters:
@@ -11447,7 +11447,7 @@ actions:
property: DisableAutoExclusions # Status: Get-MpPreference | Select-Object -Property DisableAutoExclusions
value: $True # Set: Set-MpPreference -Force -DisableAutoExclusions $True
default: $False # Default: False | Remove-MpPreference -Force -DisableAutoExclusions | Set-MpPreference -Force -DisableAutoExclusions $False
setDefaultOnWindows11: true # `Remove-MpPreference` has no affect (does not change the value) in Windows 11
setDefaultOnWindows11: 'true' # `Remove-MpPreference` has no affect (does not change the value) in Windows 11
-
function: SetRegistryValue
parameters:
@@ -11938,7 +11938,7 @@ actions:
property: ScanParameters # Status: Get-MpPreference | Select-Object -Property ScanParameters
value: "'1'" # Set: Set-MpPreference -Force -ScanParameters '1'
default: "'1'" # Default: 1 | Remove-MpPreference -Force -ScanParameters | Set-MpPreference -Force -ScanParameters '1'
setDefaultOnWindows11: true # ❌ Remove-MpPreference with -ScanParameters fails due to a buggy behavior where it tries to set it to True on Windows 11
setDefaultOnWindows11: 'true' # ❌ Remove-MpPreference with -ScanParameters fails due to a buggy behavior where it tries to set it to True on Windows 11
-
name: Minimize daily quick scan frequency
docs: https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.WindowsDefender::Scan_QuickScanInterval
@@ -12819,7 +12819,7 @@ actions:
# function: SoftDeleteFiles
# parameters:
# fileGlob: '%PROGRAMFILES%\Windows Defender\MsMpEng.exe' # Found also in C:\ProgramData\Microsoft\Windows Defender\Platform\4.18.2107.4-0 and \4.18.2103.7-0 ...
# grantPermissions: true # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 22H2
# grantPermissions: 'true' # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 22H2
-
category: Disable Defender kernel-level drivers
children:
@@ -12840,7 +12840,7 @@ actions:
function: SoftDeleteFiles
parameters:
fileGlob: '%SYSTEMROOT%\System32\drivers\WdNisDrv.sys'
grantPermissions: true # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 22H2
grantPermissions: 'true' # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 22H2
-
name: Disable "Microsoft Defender Antivirus Mini-Filter Driver" service
docs:
@@ -12858,7 +12858,7 @@ actions:
function: SoftDeleteFiles
parameters:
fileGlob: '%SYSTEMROOT%\System32\drivers\WdFilter.sys'
grantPermissions: true # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 22H2
grantPermissions: 'true' # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 22H2
-
name: Disable "Microsoft Defender Antivirus Boot Driver" service
docs: https://web.archive.org/web/20240314062057/https://batcmd.com/windows/10/services/wdboot/
@@ -12874,7 +12874,7 @@ actions:
function: SoftDeleteFiles
parameters:
fileGlob: '%SYSTEMROOT%\System32\drivers\WdBoot.sys'
grantPermissions: true # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 22H2
grantPermissions: 'true' # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 22H2
-
name: Disable "Microsoft Defender Antivirus Network Inspection" service
docs:
@@ -12890,7 +12890,7 @@ actions:
# function: SoftDeleteFiles
# parameters:
# fileGlob: '%PROGRAMFILES%\Windows Defender\NisSrv.exe' # Found also in C:\ProgramData\Microsoft\Windows Defender\Platform\4.18.2107.4-0 and \4.18.2103.7-0 ...
# grantPermissions: true # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 22H2
# grantPermissions: 'true' # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 22H2
-
name: Disable "Windows Defender Advanced Threat Protection Service" service
docs: https://web.archive.org/web/20240314091443/https://batcmd.com/windows/10/services/sense/
@@ -12904,7 +12904,7 @@ actions:
function: SoftDeleteFiles
parameters:
fileGlob: '%PROGRAMFILES%\Windows Defender Advanced Threat Protection\MsSense.exe'
grantPermissions: true # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 22H2
grantPermissions: 'true' # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 22H2
-
name: Disable "Windows Security Service" service
docs: |-
@@ -12939,7 +12939,7 @@ actions:
function: SoftDeleteFiles
parameters:
fileGlob: '%WINDIR%\System32\SecurityHealthService.exe'
grantPermissions: true # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 22H2
grantPermissions: 'true' # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 22H2
-
category: Disable SmartScreen
docs:
@@ -13411,7 +13411,7 @@ actions:
function: SoftDeleteFiles
parameters:
fileGlob: '%SYSTEMROOT%\System32\WaaSMedicSvc.dll'
grantPermissions: true # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 23H2
grantPermissions: 'true' # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 23H2
-
function: TerminateAndBlockExecution
parameters:
@@ -13420,38 +13420,38 @@ actions:
function: SoftDeleteFiles
parameters:
fileGlob: '%SYSTEMROOT%\System32\WaaSMedicAgent.exe'
grantPermissions: true # 🔒️ Protected on Windows 10 since 22H2 | 🔍 Missing on Windows 11 since 23H2
grantPermissions: 'true' # 🔒️ Protected on Windows 10 since 22H2 | 🔍 Missing on Windows 11 since 23H2
-
function: SoftDeleteFiles
parameters:
fileGlob: '%SYSTEMROOT%\System32\WaaSMedicCapsule.dll'
grantPermissions: true # 🔒️ Protected on Windows 10 since 22H2 | 🔍 Missing on Windows 11 since 23H2
grantPermissions: 'true' # 🔒️ Protected on Windows 10 since 22H2 | 🔍 Missing on Windows 11 since 23H2
-
function: SoftDeleteFiles
parameters:
fileGlob: '%SYSTEMROOT%\System32\WaaSMedicPS.dll'
grantPermissions: true # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 23H2
grantPermissions: 'true' # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 23H2
-
function: SoftDeleteFiles
parameters:
fileGlob: '%SYSTEMROOT%\System32\WaaSAssessment.dll'
grantPermissions: true # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 23H2
grantPermissions: 'true' # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 23H2
-
function: SoftDeleteFiles
parameters:
fileGlob: '%SYSTEMROOT%\System32\Windows.Internal.WaaSMedicDocked.dll'
grantPermissions: true # 🔍 Missing on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 23H2
grantPermissions: 'true' # 🔍 Missing on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 23H2
-
function: SoftDeleteFiles
parameters:
fileGlob: '%WINDIR%\UUS\amd64\WaaSMedicSvcImpl.dll'
grantPermissions: true # 🔍 Missing on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 23H2
grantPermissions: 'true' # 🔍 Missing on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 23H2
-
function: SoftDeleteFiles
parameters:
fileGlob: '%WINDIR%\WaaS\*' # Includes `services` and `tasks` folders that defines the desired state configuration on remediation.
grantPermissions: true # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 23H2
recurse: true
grantPermissions: 'true' # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 23H2
recurse: 'true'
-
name: Disable automatically enabling Windows Update Medic Service
recommend: strict
@@ -13491,7 +13491,7 @@ actions:
function: SoftDeleteFiles
parameters:
fileGlob: '%SYSTEMROOT%\System32\upfc.exe'
grantPermissions: true # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 23H2
grantPermissions: 'true' # 🔒️ Protected on Windows 10 since 22H2 | 🔒️ Protected on Windows 11 since 23H2
beforeIteration: |- # Skip Windows versions older than Windows 10 22H2 (build number 19045) to avoid reported blue screen issues.
$osVersion = [System.Environment]::OSVersion.Version
function Test-IsBeforeWin10Version22H2 { ($osVersion.Major -lt 10) -or (($osVersion.Major -eq 10) -and ($osVersion.Build -lt 19045)) }
@@ -13658,7 +13658,7 @@ actions:
# Check: Get-ScheduledTask -TaskPath '\Microsoft\Windows\InstallService\' -TaskName 'WakeUpAndContinueUpdates'
taskPathPattern: \Microsoft\Windows\InstallService\
taskNamePattern: WakeUpAndContinueUpdates
disableOnRevert: true
disableOnRevert: 'true'
-
name: Disable "WakeUpAndScanForUpdates" task
docs: |-
@@ -13681,7 +13681,7 @@ actions:
# Check: Get-ScheduledTask -TaskPath '\Microsoft\Windows\InstallService\' -TaskName 'WakeUpAndScanForUpdates'
taskPathPattern: \Microsoft\Windows\InstallService\
taskNamePattern: WakeUpAndScanForUpdates
disableOnRevert: true
disableOnRevert: 'true'
-
name: Disable "Scheduled Start" task
docs: |-
@@ -13734,7 +13734,7 @@ actions:
# Check: Get-ScheduledTask -TaskPath '\Microsoft\Windows\UpdateOrchestrator\' -TaskName 'Report policies'
taskPathPattern: \Microsoft\Windows\UpdateOrchestrator\
taskNamePattern: Report policies
grantPermissions: true # 🔒 No permissions, tested since [≥ Windows 10 22H2] [≥ Windows 11 22H2]
grantPermissions: 'true' # 🔒 No permissions, tested since [≥ Windows 10 22H2] [≥ Windows 11 22H2]
-
name: Disable "Schedule Maintenance Work" task
docs: |-
@@ -13760,8 +13760,8 @@ actions:
# Check: Get-ScheduledTask -TaskPath '\Microsoft\Windows\UpdateOrchestrator\' -TaskName 'Schedule Maintenance Work'
taskPathPattern: \Microsoft\Windows\UpdateOrchestrator\
taskNamePattern: Schedule Maintenance Work
disableOnRevert: true
grantPermissions: true # 🔒 No permissions, tested since [≥ Windows 10 22H2] [≥ Windows 11 22H2]
disableOnRevert: 'true'
grantPermissions: 'true' # 🔒 No permissions, tested since [≥ Windows 10 22H2] [≥ Windows 11 22H2]
-
name: Disable "Schedule Scan" task
docs: |-
@@ -13786,7 +13786,7 @@ actions:
# Check: Get-ScheduledTask -TaskPath '\Microsoft\Windows\UpdateOrchestrator\' -TaskName 'Schedule Scan'
taskPathPattern: \Microsoft\Windows\UpdateOrchestrator\
taskNamePattern: Schedule Scan
grantPermissions: true # 🔒 No permissions, tested since [≥ Windows 10 22H2] [≥ Windows 11 22H2]
grantPermissions: 'true' # 🔒 No permissions, tested since [≥ Windows 10 22H2] [≥ Windows 11 22H2]
-
name: Disable "Schedule Scan Static Task" task
docs: |-
@@ -13812,7 +13812,7 @@ actions:
# Check: Get-ScheduledTask -TaskPath '\Microsoft\Windows\UpdateOrchestrator\' -TaskName 'Schedule Scan Static Task'
taskPathPattern: \Microsoft\Windows\UpdateOrchestrator\
taskNamePattern: Schedule Scan Static Task
grantPermissions: true # 🔒 No permissions, tested since [≥ Windows 10 22H2] [≥ Windows 11 22H2]
grantPermissions: 'true' # 🔒 No permissions, tested since [≥ Windows 10 22H2] [≥ Windows 11 22H2]
-
name: Disable "Schedule Wake To Work" task
docs: |-
@@ -13837,8 +13837,8 @@ actions:
# Check: Get-ScheduledTask -TaskPath '\Microsoft\Windows\UpdateOrchestrator\' -TaskName 'Schedule Wake To Work'
taskPathPattern: \Microsoft\Windows\UpdateOrchestrator\
taskNamePattern: Schedule Wake To Work
disableOnRevert: true
grantPermissions: true # 🔒 No permissions, tested since [≥ Windows 10 22H2] [≥ Windows 11 22H2]
disableOnRevert: 'true'
grantPermissions: 'true' # 🔒 No permissions, tested since [≥ Windows 10 22H2] [≥ Windows 11 22H2]
-
name: Disable "Schedule Work" task
docs: |-
@@ -13863,8 +13863,8 @@ actions:
# Check: Get-ScheduledTask -TaskPath '\Microsoft\Windows\UpdateOrchestrator\' -TaskName 'Schedule Work'
taskPathPattern: \Microsoft\Windows\UpdateOrchestrator\
taskNamePattern: Schedule Work
disableOnRevert: true
grantPermissions: true # 🔒 No permissions, tested since [≥ Windows 10 22H2] [≥ Windows 11 22H2]
disableOnRevert: 'true'
grantPermissions: 'true' # 🔒 No permissions, tested since [≥ Windows 10 22H2] [≥ Windows 11 22H2]
-
name: Disable "UpdateModelTask" task
docs: |-
@@ -13894,7 +13894,7 @@ actions:
# Check: Get-ScheduledTask -TaskPath '\Microsoft\Windows\UpdateOrchestrator\' -TaskName 'UpdateModelTask'
taskPathPattern: \Microsoft\Windows\UpdateOrchestrator\
taskNamePattern: UpdateModelTask
grantPermissions: true # 🔒 No permissions, tested since [≥ Windows 10 22H2]
grantPermissions: 'true' # 🔒 No permissions, tested since [≥ Windows 10 22H2]
-
name: Disable "Start Oobe Expedite Work" task
docs: |-
@@ -13921,7 +13921,7 @@ actions:
# Check: Get-ScheduledTask -TaskPath '\Microsoft\Windows\UpdateOrchestrator\' -TaskName 'Start Oobe Expedite Work'
taskPathPattern: \Microsoft\Windows\UpdateOrchestrator\
taskNamePattern: Start Oobe Expedite Work
grantPermissions: true # 🔒 No permissions, Tested since [≥ Windows 11 22H2]
grantPermissions: 'true' # 🔒 No permissions, Tested since [≥ Windows 11 22H2]
-
name: Disable "StartOobeAppsScan_LicenseAccepted" task
docs: |-
@@ -13948,7 +13948,7 @@ actions:
# Check: Get-ScheduledTask -TaskPath '\Microsoft\Windows\UpdateOrchestrator\' -TaskName 'StartOobeAppsScan_LicenseAccepted'
taskPathPattern: \Microsoft\Windows\UpdateOrchestrator\
taskNamePattern: StartOobeAppsScan_LicenseAccepted
grantPermissions: true # 🔒 No permissions, tested since [≥ Windows 11 22H2]
grantPermissions: 'true' # 🔒 No permissions, tested since [≥ Windows 11 22H2]
-
name: Disable "StartOobeAppsScan_OobeAppReady" task
docs: |-
@@ -13975,7 +13975,7 @@ actions:
# Check: Get-ScheduledTask -TaskPath '\Microsoft\Windows\UpdateOrchestrator\' -TaskName 'StartOobeAppsScan_OobeAppReady'
taskPathPattern: \Microsoft\Windows\UpdateOrchestrator\
taskNamePattern: StartOobeAppsScan_OobeAppReady
grantPermissions: true # 🔒 No permissions, tested since [≥ Windows 11 22H2]
grantPermissions: 'true' # 🔒 No permissions, tested since [≥ Windows 11 22H2]
-
name: Disable "StartOobeAppsScanAfterUpdate" task
docs: |-
@@ -14002,7 +14002,7 @@ actions:
# Check: Get-ScheduledTask -TaskPath '\Microsoft\Windows\UpdateOrchestrator\' -TaskName 'StartOobeAppsScanAfterUpdate'
taskPathPattern: \Microsoft\Windows\UpdateOrchestrator\
taskNamePattern: StartOobeAppsScanAfterUpdate
grantPermissions: true # 🔒 No permissions, tested since [≥ Windows 11 22H2]
grantPermissions: 'true' # 🔒 No permissions, tested since [≥ Windows 11 22H2]
-
name: Disable "USO_UxBroker" task
docs: |-
@@ -14033,7 +14033,7 @@ actions:
# Check: Get-ScheduledTask -TaskPath '\Microsoft\Windows\UpdateOrchestrator\' -TaskName 'USO_UxBroker'
taskPathPattern: \Microsoft\Windows\UpdateOrchestrator\
taskNamePattern: USO_UxBroker
grantPermissions: true # 🔒 No permissions, tested since [≥ Windows 10 22H2] [≥ Windows 11 22H2]
grantPermissions: 'true' # 🔒 No permissions, tested since [≥ Windows 10 22H2] [≥ Windows 11 22H2]
-
name: Disable "UUS Failover Task" task
docs: |-
@@ -14060,7 +14060,7 @@ actions:
# Check: Get-ScheduledTask -TaskPath '\Microsoft\Windows\UpdateOrchestrator\' -TaskName 'UUS Failover Task'
taskPathPattern: \Microsoft\Windows\UpdateOrchestrator\
taskNamePattern: UUS Failover Task
grantPermissions: true # 🔒 No permissions, tested since [≥ Windows 11 22H2]
grantPermissions: 'true' # 🔒 No permissions, tested since [≥ Windows 11 22H2]
-
name: Disable "PerformRemediation" task
docs: |-
@@ -14093,7 +14093,7 @@ actions:
# Check: Get-ScheduledTask -TaskPath '\Microsoft\Windows\WaaSMedic\' -TaskName 'PerformRemediation'
taskPathPattern: \Microsoft\Windows\WaaSMedic\
taskNamePattern: PerformRemediation
grantPermissions: true # 🔒 No permissions, tested since [≥ Windows 10 22H2] [≥ Windows 11 22H2]
grantPermissions: 'true' # 🔒 No permissions, tested since [≥ Windows 10 22H2] [≥ Windows 11 22H2]
-
name: Disable outdated Windows Update tasks
docs: |-
@@ -19014,7 +19014,7 @@ actions:
function: DeleteDirectory
parameters:
directoryGlob: '%LOCALAPPDATA%\Microsoft\OneDrive'
grantPermissions: true
grantPermissions: 'true'
-
function: DeleteDirectory
parameters:
@@ -19465,7 +19465,7 @@ actions:
function: DisableWindowsFeature
parameters:
featureName: DirectPlay # Get-WindowsOptionalFeature -FeatureName 'DirectPlay' -Online
disabledByDefault: true
disabledByDefault: 'true'
-
name: Disable "Internet Explorer" feature
docs: |-
@@ -19483,17 +19483,17 @@ actions:
function: DisableWindowsFeature
parameters:
featureName: Internet-Explorer-Optional-x64 # Get-WindowsOptionalFeature -FeatureName 'Internet-Explorer-Optional-x64' -Online
ignoreMissingOnRevert: true
ignoreMissingOnRevert: 'true'
-
function: DisableWindowsFeature
parameters:
featureName: Internet-Explorer-Optional-x84 # Get-WindowsOptionalFeature -FeatureName 'Internet-Explorer-Optional-x84' -Online
ignoreMissingOnRevert: true
ignoreMissingOnRevert: 'true'
-
function: DisableWindowsFeature
parameters:
featureName: Internet-Explorer-Optional-amd64 # Get-WindowsOptionalFeature -FeatureName 'Internet-Explorer-Optional-amd64' -Online
ignoreMissingOnRevert: true
ignoreMissingOnRevert: 'true'
-
name: Disable "Legacy Components" feature
docs: |-
@@ -19510,7 +19510,7 @@ actions:
function: DisableWindowsFeature
parameters:
featureName: LegacyComponents # Get-WindowsOptionalFeature -FeatureName 'LegacyComponents' -Online
disabledByDefault: true
disabledByDefault: 'true'
-
category: Disable Hyper-V virtualization features
children:
@@ -19530,7 +19530,7 @@ actions:
function: DisableWindowsFeature
parameters:
featureName: Microsoft-Hyper-V-All # Get-WindowsOptionalFeature -FeatureName 'Microsoft-Hyper-V-All' -Online
disabledByDefault: true
disabledByDefault: 'true'
-
name: Disable "Hyper-V GUI Management Tools" feature
docs: |-
@@ -19547,7 +19547,7 @@ actions:
function: DisableWindowsFeature
parameters:
featureName: Microsoft-Hyper-V-Management-Clients # Get-WindowsOptionalFeature -FeatureName 'Microsoft-Hyper-V-Management-Clients' -Online
disabledByDefault: true
disabledByDefault: 'true'
-
name: Disable "Hyper-V Management Tools" feature
docs: |-
@@ -19564,7 +19564,7 @@ actions:
function: DisableWindowsFeature
parameters:
featureName: Microsoft-Hyper-V-Tools-All # Get-WindowsOptionalFeature -FeatureName 'Microsoft-Hyper-V-Tools-All' -Online
disabledByDefault: true # Default: Disabled (tested: Windows 10 22H2, Windows 11 23H2)
disabledByDefault: 'true' # Default: Disabled (tested: Windows 10 22H2, Windows 11 23H2)
-
name: Disable "Hyper-V Module for Windows PowerShell" feature
docs: |-
@@ -19581,7 +19581,7 @@ actions:
function: DisableWindowsFeature
parameters:
featureName: Microsoft-Hyper-V-Management-PowerShell # Get-WindowsOptionalFeature -FeatureName 'Microsoft-Hyper-V-Management-PowerShell' -Online
disabledByDefault: true
disabledByDefault: 'true'
-
category: Disable printing features
children:
@@ -19620,7 +19620,7 @@ actions:
function: DisableWindowsFeature
parameters:
featureName: Printing-Foundation-LPDPrintService # Get-WindowsOptionalFeature -FeatureName 'Printing-Foundation-LPDPrintService' -Online
disabledByDefault: true
disabledByDefault: 'true'
-
name: Disable "LPR Port Monitor" feature
docs: |-
@@ -19637,7 +19637,7 @@ actions:
function: DisableWindowsFeature
parameters:
featureName: Printing-Foundation-LPRPortMonitor # Get-WindowsOptionalFeature -FeatureName 'Printing-Foundation-LPRPortMonitor' -Online
disabledByDefault: true
disabledByDefault: 'true'
-
name: Disable "Microsoft Print to PDF" feature
docs: |-
@@ -19707,7 +19707,7 @@ actions:
function: DisableWindowsFeature
parameters:
featureName: Printing-XPSServices-Features # Get-WindowsOptionalFeature -FeatureName 'Printing-XPSServices-Features' -Online
disabledByDefault: true
disabledByDefault: 'true'
-
name: Disable "XPS Viewer" feature
recommend: standard # Deprecated and missing on modern versions of Windows
@@ -19729,7 +19729,7 @@ actions:
function: DisableWindowsFeature
parameters:
featureName: Xps-Foundation-Xps-Viewer # Get-WindowsOptionalFeature -FeatureName 'Xps-Foundation-Xps-Viewer' -Online
ignoreMissingOnRevert: true
ignoreMissingOnRevert: 'true'
-
name: Disable "Media Features" feature
docs: |-
@@ -19767,7 +19767,7 @@ actions:
function: DisableWindowsFeature
parameters:
featureName: ScanManagementConsole # Get-WindowsOptionalFeature -FeatureName 'ScanManagementConsole' -Online
ignoreMissingOnRevert: true
ignoreMissingOnRevert: 'true'
-
name: Disable "Windows Fax and Scan" feature
recommend: standard # Deprecated and missing on modern versions of Windows
@@ -19789,7 +19789,7 @@ actions:
function: DisableWindowsFeature
parameters:
featureName: FaxServicesClientPackage # Get-WindowsOptionalFeature -FeatureName 'FaxServicesClientPackage' -Online
ignoreMissingOnRevert: true
ignoreMissingOnRevert: 'true'
-
name: Disable "Windows Media Player" feature
docs: |-
@@ -20667,8 +20667,8 @@ actions:
function: ShowMessage
parameters:
message: Disabling Network settings on Windows 10 is known to break Network settings.
ignoreWindows11: true
warn: true
ignoreWindows11: 'true'
warn: 'true'
-
function: DisableService
parameters:

View File

@@ -0,0 +1,51 @@
import { describe, it, expect } from 'vitest';
import { validateParameterName } from '@/application/Parser/Executable/Script/Compiler/Function/Shared/ParameterNameValidator';
describe('ParameterNameValidator', () => {
describe('accepts when valid', () => {
// arrange
const validValues: readonly string[] = [
'lowercase',
'onlyLetters',
'l3tt3rsW1thNumb3rs',
];
validValues.forEach((validValue) => {
it(validValue, () => {
// act
const act = () => validateParameterName(validValue);
// assert
expect(act).to.not.throw();
});
});
});
describe('throws if invalid', () => {
// arrange
const testScenarios: readonly {
readonly description: string;
readonly value: string;
}[] = [
{
description: 'empty name',
value: '',
},
{
description: 'has @',
value: 'b@d',
},
{
description: 'has {',
value: 'b{a}d',
},
];
testScenarios.forEach((
{ description, value },
) => {
it(description, () => {
// act
const act = () => validateParameterName(value);
// assert
expect(act).to.throw();
});
});
});
});

View File

@@ -1,6 +1,6 @@
import { describe, it, expect } from 'vitest';
import { createTypeValidator } from '@/application/Parser/Common/TypeValidator';
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
import { createTypeValidator, type NonEmptyStringAssertion, type RegexValidationRule } from '@/application/Parser/Common/TypeValidator';
import { itEachAbsentObjectValue, itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
describe('createTypeValidator', () => {
describe('assertObject', () => {
@@ -146,6 +146,108 @@ describe('createTypeValidator', () => {
});
});
});
describe('assertNonEmptyString', () => {
describe('with valid string', () => {
it('accepts non-empty string without regex rule', () => {
// arrange
const nonEmptyString = 'hello';
const { assertNonEmptyString } = createTypeValidator();
// act
const act = () => assertNonEmptyString({ value: nonEmptyString, valueName: 'unimportant name' });
// assert
expect(act).to.not.throw();
});
it('accepts if the string matches the regex', () => {
// arrange
const regex: RegExp = /goodbye/;
const stringMatchingRegex = 'Valid string containing "goodbye"';
const rule: RegexValidationRule = {
expectedMatch: regex,
errorMessage: 'String contain "goodbye"',
};
const { assertNonEmptyString } = createTypeValidator();
// act
const act = () => assertNonEmptyString({
value: stringMatchingRegex,
valueName: 'unimportant name',
rule,
});
// assert
expect(act).to.not.throw();
});
});
describe('with invalid string', () => {
describe('throws error for missing string', () => {
itEachAbsentStringValue((absentValue) => {
// arrange
const valueName = 'absent string value';
const expectedMessage = `'${valueName}' is missing.`;
const { assertNonEmptyString } = createTypeValidator();
// act
const act = () => assertNonEmptyString({ value: absentValue, valueName });
// assert
expect(act).to.throw(expectedMessage);
});
});
describe('throws error for non string values', () => {
// arrange
const testScenarios: readonly {
readonly description: string;
readonly invalidValue: unknown;
}[] = [
{
description: 'number',
invalidValue: 42,
},
{
description: 'boolean',
invalidValue: true,
},
{
description: 'object',
invalidValue: { property: 'value' },
},
{
description: 'array',
invalidValue: ['a', 'r', 'r', 'a', 'y'],
},
];
testScenarios.forEach(({
description, invalidValue,
}) => {
it(description, () => {
const valueName = 'invalidValue';
const expectedMessage = `'${valueName}' should be of type 'string', but is of type '${typeof invalidValue}'.`;
const { assertNonEmptyString } = createTypeValidator();
// act
const act = () => assertNonEmptyString({ value: invalidValue, valueName });
// assert
expect(act).to.throw(expectedMessage);
});
});
});
it('throws an error if the string does not match the regex', () => {
// arrange
const regex: RegExp = /goodbye/;
const stringNotMatchingRegex = 'Hello';
const expectedMessage = 'String should contain "goodbye"';
const rule: RegexValidationRule = {
expectedMatch: regex,
errorMessage: expectedMessage,
};
const assertion: NonEmptyStringAssertion = {
value: stringNotMatchingRegex,
valueName: 'non-important-value-name',
rule,
};
const { assertNonEmptyString } = createTypeValidator();
// act
const act = () => assertNonEmptyString(assertion);
// assert
expect(act).to.throw(expectedMessage);
});
});
});
});
function createObjectWithProperties(properties: readonly string[]): object {

View File

@@ -1,52 +1,110 @@
import { describe, expect } from 'vitest';
import { FunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
import { testParameterName } from '../../../ParameterNameTestRunner';
import { describe, expect, it } from 'vitest';
import { createFunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
import { TypeValidatorStub } from '@tests/unit/shared/Stubs/TypeValidatorStub';
import type { NonEmptyStringAssertion, TypeValidator } from '@/application/Parser/Common/TypeValidator';
import { createParameterNameValidatorStub } from '@tests/unit/shared/Stubs/ParameterNameValidatorStub';
import type { ParameterNameValidator } from '@/application/Parser/Executable/Script/Compiler/Function/Shared/ParameterNameValidator';
describe('FunctionCallArgument', () => {
describe('ctor', () => {
describe('createFunctionCallArgument', () => {
describe('parameter name', () => {
testParameterName(
(parameterName) => new FunctionCallArgumentBuilder()
.withParameterName(parameterName)
.build()
.parameterName,
);
});
describe('throws if argument value is absent', () => {
itEachAbsentStringValue((absentValue) => {
it('assigns correctly', () => {
// arrange
const parameterName = 'paramName';
const expectedError = `Missing argument value for the parameter "${parameterName}".`;
const argumentValue = absentValue;
const expectedName = 'expected parameter name';
const context = new TestContext()
.withParameterName(expectedName);
// act
const act = () => new FunctionCallArgumentBuilder()
.withParameterName(parameterName)
.withArgumentValue(argumentValue)
.build();
const actualArgument = context.create();
// assert
expect(act).to.throw(expectedError);
}, { excludeNull: true, excludeUndefined: true });
const actualName = actualArgument.parameterName;
expect(actualName).toEqual(expectedName);
});
it('validates parameter name', () => {
// arrange
const validator = createParameterNameValidatorStub();
const expectedParameterName = 'parameter name expected to be validated';
const context = new TestContext()
.withParameterName(expectedParameterName)
.withParameterNameValidator(validator.validator);
// act
context.create();
// assert
expect(validator.validatedNames).to.have.lengthOf(1);
expect(validator.validatedNames).to.include(expectedParameterName);
});
});
describe('argument value', () => {
it('assigns correctly', () => {
// arrange
const expectedValue = 'expected argument value';
const context = new TestContext()
.withArgumentValue(expectedValue);
// act
const actualArgument = context.create();
// assert
const actualValue = actualArgument.argumentValue;
expect(actualValue).toEqual(expectedValue);
});
it('validates argument value', () => {
// arrange
const parameterNameInError = 'expected parameter with argument error';
const expectedArgumentValue = 'argument value to be validated';
const expectedAssertion: NonEmptyStringAssertion = {
value: expectedArgumentValue,
valueName: `Missing argument value for the parameter "${parameterNameInError}".`,
};
const typeValidator = new TypeValidatorStub();
const context = new TestContext()
.withArgumentValue(expectedArgumentValue)
.withParameterName(parameterNameInError)
.withTypeValidator(typeValidator);
// act
context.create();
// assert
typeValidator.assertNonEmptyString(expectedAssertion);
});
});
});
});
class FunctionCallArgumentBuilder {
private parameterName = 'default-parameter-name';
class TestContext {
private parameterName = `[${TestContext.name}] default-parameter-name`;
private argumentValue = 'default-argument-value';
private argumentValue = `[${TestContext.name}] default-argument-value`;
public withParameterName(parameterName: string) {
private typeValidator: TypeValidator = new TypeValidatorStub();
private parameterNameValidator
: ParameterNameValidator = createParameterNameValidatorStub().validator;
public withParameterName(parameterName: string): this {
this.parameterName = parameterName;
return this;
}
public withArgumentValue(argumentValue: string) {
public withArgumentValue(argumentValue: string): this {
this.argumentValue = argumentValue;
return this;
}
public build() {
return new FunctionCallArgument(this.parameterName, this.argumentValue);
public withTypeValidator(typeValidator: TypeValidator): this {
this.typeValidator = typeValidator;
return this;
}
public withParameterNameValidator(parameterNameValidator: ParameterNameValidator): this {
this.parameterNameValidator = parameterNameValidator;
return this;
}
public create(): ReturnType<typeof createFunctionCallArgument> {
return createFunctionCallArgument(
this.parameterName,
this.argumentValue,
{
typeValidator: this.typeValidator,
validateParameterName: this.parameterNameValidator,
},
);
}
}

View File

@@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest';
import { FunctionCallArgumentCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
import { FunctionCallArgumentStub } from '@tests/unit/shared/Stubs/FunctionCallArgumentStub';
import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
import type { IFunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/IFunctionCallArgument';
import type { FunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
describe('FunctionCallArgumentCollection', () => {
describe('addArgument', () => {
@@ -25,7 +25,7 @@ describe('FunctionCallArgumentCollection', () => {
// arrange
const testCases: ReadonlyArray<{
readonly description: string;
readonly args: readonly IFunctionCallArgument[];
readonly args: readonly FunctionCallArgument[];
readonly expectedParameterNames: string[];
}> = [{
description: 'no args',

View File

@@ -14,6 +14,8 @@ import { SharedFunctionCollectionStub } from '@tests/unit/shared/Stubs/SharedFun
import { itThrowsContextualError } from '@tests/unit/application/Parser/Common/ContextualErrorTester';
import type { ErrorWithContextWrapper } from '@/application/Parser/Common/ContextualError';
import { errorWithContextWrapperStub } from '@tests/unit/shared/Stubs/ErrorWithContextWrapperStub';
import type { FunctionCallArgumentFactory } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
import { FunctionCallArgumentFactoryStub } from '../../../../../../../../../../../shared/Stubs/FunctionCallArgumentFactoryStub';
describe('NestedFunctionArgumentCompiler', () => {
describe('createCompiledNestedCall', () => {
@@ -266,6 +268,9 @@ class NestedFunctionArgumentCompilerBuilder implements ArgumentCompiler {
private wrapError: ErrorWithContextWrapper = errorWithContextWrapperStub;
private callArgumentFactory
: FunctionCallArgumentFactory = new FunctionCallArgumentFactoryStub().factory;
public withExpressionsCompiler(expressionsCompiler: IExpressionsCompiler): this {
this.expressionsCompiler = expressionsCompiler;
return this;
@@ -292,10 +297,11 @@ class NestedFunctionArgumentCompilerBuilder implements ArgumentCompiler {
}
public createCompiledNestedCall(): FunctionCall {
const compiler = new NestedFunctionArgumentCompiler(
this.expressionsCompiler,
this.wrapError,
);
const compiler = new NestedFunctionArgumentCompiler({
expressionsCompiler: this.expressionsCompiler,
wrapError: this.wrapError,
createCallArgument: this.callArgumentFactory,
});
return compiler.createCompiledNestedCall(
this.nestedFunctionCall,
this.parentFunctionCall,

View File

@@ -3,8 +3,12 @@ import type { FunctionCallsData, FunctionCallData } from '@/application/collecti
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 type {
NonEmptyCollectionAssertion, ObjectAssertion, TypeValidator,
} from '@/application/Parser/Common/TypeValidator';
import { TypeValidatorStub } from '@tests/unit/shared/Stubs/TypeValidatorStub';
import type { FunctionCallArgumentFactory } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
import { FunctionCallArgumentFactoryStub } from '@tests/unit/shared/Stubs/FunctionCallArgumentFactoryStub';
describe('FunctionCallsParser', () => {
describe('parseFunctionCalls', () => {
@@ -174,12 +178,15 @@ describe('FunctionCallsParser', () => {
});
class TestContext {
private validator: TypeValidator = new TypeValidatorStub();
private typeValidator: TypeValidator = new TypeValidatorStub();
private createCallArgument
: FunctionCallArgumentFactory = new FunctionCallArgumentFactoryStub().factory;
private calls: FunctionCallsData = [new FunctionCallDataStub()];
public withTypeValidator(typeValidator: TypeValidator): this {
this.validator = typeValidator;
this.typeValidator = typeValidator;
return this;
}
@@ -191,7 +198,10 @@ class TestContext {
public parse(): ReturnType<typeof parseFunctionCalls> {
return parseFunctionCalls(
this.calls,
this.validator,
{
typeValidator: this.typeValidator,
createCallArgument: this.createCallArgument,
},
);
}
}

View File

@@ -1,50 +0,0 @@
import { describe, it, expect } from 'vitest';
import { FunctionParameter } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameter';
import { testParameterName } from '../../ParameterNameTestRunner';
describe('FunctionParameter', () => {
describe('name', () => {
testParameterName(
(parameterName) => new FunctionParameterBuilder()
.withName(parameterName)
.build()
.name,
);
});
describe('isOptional', () => {
describe('sets as expected', () => {
// arrange
const expectedValues = [true, false];
for (const expected of expectedValues) {
it(expected.toString(), () => {
// act
const sut = new FunctionParameterBuilder()
.withIsOptional(expected)
.build();
// expect
expect(sut.isOptional).to.equal(expected);
});
}
});
});
});
class FunctionParameterBuilder {
private name = 'parameterFromParameterBuilder';
private isOptional = false;
public withName(name: string) {
this.name = name;
return this;
}
public withIsOptional(isOptional: boolean) {
this.isOptional = isOptional;
return this;
}
public build() {
return new FunctionParameter(this.name, this.isOptional);
}
}

View File

@@ -0,0 +1,83 @@
import { describe, it, expect } from 'vitest';
import type { ParameterDefinitionData } from '@/application/collections/';
import type { ParameterNameValidator } from '@/application/Parser/Executable/Script/Compiler/Function/Shared/ParameterNameValidator';
import { createParameterNameValidatorStub } from '@tests/unit/shared/Stubs/ParameterNameValidatorStub';
import { parseFunctionParameter } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterParser';
import { ParameterDefinitionDataStub } from '@tests/unit/shared/Stubs/ParameterDefinitionDataStub';
describe('FunctionParameterParser', () => {
describe('parseFunctionParameter', () => {
describe('name', () => {
it('assigns correctly', () => {
// arrange
const expectedName = 'expected-function-name';
const data = new ParameterDefinitionDataStub()
.withName(expectedName);
// act
const actual = new TestContext()
.withData(data)
.parse();
// expect
const actualName = actual.name;
expect(actualName).to.equal(expectedName);
});
it('validates correctly', () => {
// arrange
const expectedName = 'expected-function-name';
const { validator, validatedNames } = createParameterNameValidatorStub();
const data = new ParameterDefinitionDataStub()
.withName(expectedName);
// act
new TestContext()
.withData(data)
.withValidator(validator)
.parse();
// expect
expect(validatedNames).to.have.lengthOf(1);
expect(validatedNames).to.contain(expectedName);
});
});
describe('isOptional', () => {
describe('assigns correctly', () => {
// arrange
const expectedValues = [true, false];
for (const expected of expectedValues) {
it(expected.toString(), () => {
const data = new ParameterDefinitionDataStub()
.withOptionality(expected);
// act
const actual = new TestContext()
.withData(data)
.parse();
// expect
expect(actual.isOptional).to.equal(expected);
});
}
});
});
});
});
class TestContext {
private data: ParameterDefinitionData = new ParameterDefinitionDataStub()
.withName(`[${TestContext.name}]function-name`);
private validator: ParameterNameValidator = createParameterNameValidatorStub().validator;
public withData(data: ParameterDefinitionData) {
this.data = data;
return this;
}
public withValidator(parameterNameValidator: ParameterNameValidator): this {
this.validator = parameterNameValidator;
return this;
}
public parse() {
return parseFunctionParameter(
this.data,
this.validator,
);
}
}

View File

@@ -4,7 +4,7 @@ import type {
ParameterDefinitionData, FunctionCallsData,
} from '@/application/collections/';
import type { ISharedFunction } from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunction';
import { parseSharedFunctions, type FunctionParameterFactory } from '@/application/Parser/Executable/Script/Compiler/Function/SharedFunctionsParser';
import { parseSharedFunctions } from '@/application/Parser/Executable/Script/Compiler/Function/SharedFunctionsParser';
import { createFunctionDataWithCall, createFunctionDataWithCode, createFunctionDataWithoutCallOrCode } from '@tests/unit/shared/Stubs/FunctionDataStub';
import { ParameterDefinitionDataStub } from '@tests/unit/shared/Stubs/ParameterDefinitionDataStub';
import { FunctionCallDataStub } from '@tests/unit/shared/Stubs/FunctionCallDataStub';
@@ -16,7 +16,6 @@ import type { ICodeValidator } from '@/application/Parser/Executable/Script/Vali
import { NoEmptyLines } from '@/application/Parser/Executable/Script/Validation/Rules/NoEmptyLines';
import { NoDuplicatedLines } from '@/application/Parser/Executable/Script/Validation/Rules/NoDuplicatedLines';
import type { ErrorWithContextWrapper } from '@/application/Parser/Common/ContextualError';
import { FunctionParameterStub } from '@tests/unit/shared/Stubs/FunctionParameterStub';
import { errorWithContextWrapperStub } from '@tests/unit/shared/Stubs/ErrorWithContextWrapperStub';
import { itThrowsContextualError } from '@tests/unit/application/Parser/Common/ContextualErrorTester';
import type { FunctionParameterCollectionFactory } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterCollectionFactory';
@@ -26,6 +25,8 @@ import { createFunctionCallsParserStub } from '@tests/unit/shared/Stubs/Function
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 type { FunctionParameterParser } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterParser';
import { createFunctionParameterParserStub } from '@tests/unit/shared/Stubs/FunctionParameterParserStub';
import { expectCallsFunctionBody, expectCodeFunctionBody } from './ExpectFunctionBodyType';
describe('SharedFunctionsParser', () => {
@@ -185,7 +186,7 @@ describe('SharedFunctionsParser', () => {
const functionName = 'functionName';
const expectedErrorMessage = `Failed to create parameter: ${invalidParameterName} for function "${functionName}"`;
const expectedInnerError = new Error('injected error');
const parameterFactory: FunctionParameterFactory = () => {
const parser: FunctionParameterParser = () => {
throw expectedInnerError;
};
const functionData = createFunctionDataWithCode()
@@ -196,7 +197,7 @@ describe('SharedFunctionsParser', () => {
throwingAction: (wrapError) => {
new TestContext()
.withFunctions([functionData])
.withFunctionParameterFactory(parameterFactory)
.withFunctionParameterParser(parser)
.withErrorWrapper(wrapError)
.parseFunctions();
},
@@ -415,12 +416,7 @@ class TestContext {
private functionCallsParser: FunctionCallsParser = createFunctionCallsParserStub().parser;
private parameterFactory: FunctionParameterFactory = (
name: string,
isOptional: boolean,
) => new FunctionParameterStub()
.withName(name)
.withOptional(isOptional);
private functionParameterParser: FunctionParameterParser = createFunctionParameterParserStub;
private parameterCollectionFactory
: FunctionParameterCollectionFactory = () => new FunctionParameterCollectionStub();
@@ -450,8 +446,8 @@ class TestContext {
return this;
}
public withFunctionParameterFactory(parameterFactory: FunctionParameterFactory): this {
this.parameterFactory = parameterFactory;
public withFunctionParameterParser(functionParameterParser: FunctionParameterParser): this {
this.functionParameterParser = functionParameterParser;
return this;
}
@@ -469,7 +465,7 @@ class TestContext {
{
codeValidator: this.codeValidator,
wrapError: this.wrapError,
createParameter: this.parameterFactory,
parseParameter: this.functionParameterParser,
createParameterCollection: this.parameterCollectionFactory,
parseFunctionCalls: this.functionCallsParser,
},

View File

@@ -1,54 +0,0 @@
import { describe, it, expect } from 'vitest';
export function testParameterName(action: (parameterName: string) => string) {
describe('name', () => {
describe('sets as expected', () => {
// arrange
const expectedValues: readonly string[] = [
'lowercase',
'onlyLetters',
'l3tt3rsW1thNumb3rs',
];
for (const expected of expectedValues) {
it(expected, () => {
// act
const value = action(expected);
// assert
expect(value).to.equal(expected);
});
}
});
describe('throws if invalid', () => {
// arrange
const testScenarios: readonly {
readonly description: string;
readonly value: string;
readonly expectedError: string;
}[] = [
{
description: 'empty Name',
value: '',
expectedError: 'missing parameter name',
},
{
description: 'has @',
value: 'b@d',
expectedError: 'parameter name must be alphanumeric but it was "b@d"',
},
{
description: 'has {',
value: 'b{a}d',
expectedError: 'parameter name must be alphanumeric but it was "b{a}d"',
},
];
for (const { description, value, expectedError } of testScenarios) {
it(description, () => {
// act
const act = () => action(value);
// assert
expect(act).to.throw(expectedError);
});
}
});
});
}

View File

@@ -0,0 +1,24 @@
import { describe, it } from 'vitest';
import { TypeValidatorStub } from '@tests/unit/shared/Stubs/TypeValidatorStub';
import { validateParameterName } from '@/application/Parser/Executable/Script/Compiler/Function/Shared/ParameterNameValidator';
import type { NonEmptyStringAssertion } from '@/application/Parser/Common/TypeValidator';
describe('ParameterNameValidator', () => {
it('asserts correctly', () => {
// arrange
const parameterName = 'expected-parameter-name';
const validator = new TypeValidatorStub();
const expectedAssertion: NonEmptyStringAssertion = {
value: parameterName,
valueName: 'parameter name',
rule: {
expectedMatch: /^[0-9a-zA-Z]+$/,
errorMessage: `parameter name must be alphanumeric but it was "${parameterName}".`,
},
};
// act
validateParameterName(parameterName, validator);
// assert
validator.assertNonEmptyString(expectedAssertion);
});
});

View File

@@ -1,20 +1,23 @@
import { describe, it, expect } from 'vitest';
import { CodeSubstituter } from '@/application/Parser/ScriptingDefinition/CodeSubstituter';
import type { IExpressionsCompiler } from '@/application/Parser/Executable/Script/Compiler/Expressions/IExpressionsCompiler';
import { ProjectDetailsStub } from '@tests/unit/shared/Stubs/ProjectDetailsStub';
import { ExpressionsCompilerStub } from '@tests/unit/shared/Stubs/ExpressionsCompilerStub';
import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
import { substituteCode } from '@/application/Parser/ScriptingDefinition/CodeSubstituter';
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
import type { FunctionCallArgumentFactory } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
import { FunctionCallArgumentFactoryStub } from '@tests/unit/shared/Stubs/FunctionCallArgumentFactoryStub';
describe('CodeSubstituter', () => {
describe('substituteCode', () => {
describe('throws if code is empty', () => {
itEachAbsentStringValue((emptyCode) => {
// arrange
const expectedError = 'missing code';
const code = emptyCode;
const projectDetails = new ProjectDetailsStub();
const sut = new CodeSubstituterBuilder().build();
const context = new TestContext()
.withCode(emptyCode);
// act
const act = () => sut.substitute(code, projectDetails);
const act = () => context.substitute();
// assert
expect(act).to.throw(expectedError);
}, { excludeNull: true, excludeUndefined: true });
@@ -40,12 +43,12 @@ describe('CodeSubstituter', () => {
for (const testCase of testCases) {
it(`substitutes ${testCase.parameter} as expected`, () => {
const compilerStub = new ExpressionsCompilerStub();
const sut = new CodeSubstituterBuilder()
const context = new TestContext()
.withCompiler(compilerStub)
.withDate(date)
.build();
.withProjectDetails(projectDetails);
// act
sut.substitute('non empty code', projectDetails);
context.substitute();
// assert
expect(compilerStub.callHistory).to.have.lengthOf(1);
const parameters = compilerStub.callHistory[0].args[1];
@@ -59,33 +62,59 @@ describe('CodeSubstituter', () => {
// arrange
const expected = 'expected-code';
const compilerStub = new ExpressionsCompilerStub();
const sut = new CodeSubstituterBuilder()
const context = new TestContext()
.withCompiler(compilerStub)
.build();
.withCode(expected);
// act
sut.substitute(expected, new ProjectDetailsStub());
context.substitute();
// assert
expect(compilerStub.callHistory).to.have.lengthOf(1);
expect(compilerStub.callHistory[0].args[0]).to.equal(expected);
});
});
});
class CodeSubstituterBuilder {
class TestContext {
private compiler: IExpressionsCompiler = new ExpressionsCompilerStub();
private date = new Date();
public withCompiler(compiler: IExpressionsCompiler) {
private code = `[${TestContext.name}] default code for testing`;
private projectDetails: ProjectDetails = new ProjectDetailsStub();
private callArgumentFactory
: FunctionCallArgumentFactory = new FunctionCallArgumentFactoryStub().factory;
public withCompiler(compiler: IExpressionsCompiler): this {
this.compiler = compiler;
return this;
}
public withDate(date: Date) {
public withDate(date: Date): this {
this.date = date;
return this;
}
public build() {
return new CodeSubstituter(this.compiler, this.date);
public withCode(code: string): this {
this.code = code;
return this;
}
public withProjectDetails(projectDetails: ProjectDetails): this {
this.projectDetails = projectDetails;
return this;
}
public substitute(): ReturnType<typeof substituteCode> {
return substituteCode(
this.code,
this.projectDetails,
{
compiler: this.compiler,
provideDate: () => this.date,
createCallArgument: this.callArgumentFactory,
},
);
}
}

View File

@@ -1,7 +1,7 @@
import { describe, it, expect } from 'vitest';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import type { EnumParser } from '@/application/Common/Enum';
import type { ICodeSubstituter } from '@/application/Parser/ScriptingDefinition/ICodeSubstituter';
import type { CodeSubstituter } from '@/application/Parser/ScriptingDefinition/CodeSubstituter';
import type { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { ProjectDetailsStub } from '@tests/unit/shared/Stubs/ProjectDetailsStub';
import { EnumParserStub } from '@tests/unit/shared/Stubs/EnumParserStub';
@@ -83,7 +83,7 @@ describe('ScriptingDefinitionParser', () => {
const context = new TestContext()
.withData(data)
.withProjectDetails(projectDetails)
.withSubstituter(substituterMock);
.withSubstituter(substituterMock.substitute);
// act
const definition = context.parseScriptingDefinition();
// assert
@@ -99,7 +99,7 @@ class TestContext {
private languageParser: EnumParser<ScriptingLanguage> = new EnumParserStub<ScriptingLanguage>()
.setupDefaultValue(ScriptingLanguage.shellscript);
private codeSubstituter: ICodeSubstituter = new CodeSubstituterStub();
private codeSubstituter: CodeSubstituter = new CodeSubstituterStub().substitute;
private validator: TypeValidator = new TypeValidatorStub();
@@ -122,7 +122,7 @@ class TestContext {
return this;
}
public withSubstituter(substituter: ICodeSubstituter): this {
public withSubstituter(substituter: CodeSubstituter): this {
this.codeSubstituter = substituter;
return this;
}

View File

@@ -1,22 +1,22 @@
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
import type { ICodeSubstituter } from '@/application/Parser/ScriptingDefinition/ICodeSubstituter';
import type { CodeSubstituter } from '@/application/Parser/ScriptingDefinition/CodeSubstituter';
export class CodeSubstituterStub implements ICodeSubstituter {
export class CodeSubstituterStub {
private readonly scenarios = new Array<{
code: string, projectDetails: ProjectDetails, result: string }>();
public substitute(code: string, projectDetails: ProjectDetails): string {
public setup(code: string, projectDetails: ProjectDetails, result: string) {
this.scenarios.push({ code, projectDetails, result });
return this;
}
public substitute: CodeSubstituter = (code: string, projectDetails: ProjectDetails) => {
const scenario = this.scenarios.find(
(s) => s.code === code && s.projectDetails === projectDetails,
);
if (scenario) {
return scenario.result;
}
return `[CodeSubstituterStub] - code: ${code}`;
}
public setup(code: string, projectDetails: ProjectDetails, result: string) {
this.scenarios.push({ code, projectDetails, result });
return this;
}
return `[${CodeSubstituterStub.name}] - code: ${code}`;
};
}

View File

@@ -1,9 +1,9 @@
import type { IFunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/IFunctionCallArgument';
import type { FunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
import type { IFunctionCallArgumentCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
import { FunctionCallArgumentStub } from './FunctionCallArgumentStub';
export class FunctionCallArgumentCollectionStub implements IFunctionCallArgumentCollection {
private args = new Array<IFunctionCallArgument>();
private args = new Array<FunctionCallArgument>();
public withEmptyArguments(): this {
this.args.length = 0;
@@ -36,7 +36,7 @@ export class FunctionCallArgumentCollectionStub implements IFunctionCallArgument
return this.args.some((a) => a.parameterName === parameterName);
}
public addArgument(argument: IFunctionCallArgument): void {
public addArgument(argument: FunctionCallArgument): void {
this.args.push(argument);
}
@@ -44,7 +44,7 @@ export class FunctionCallArgumentCollectionStub implements IFunctionCallArgument
return this.args.map((a) => a.parameterName);
}
public getArgument(parameterName: string): IFunctionCallArgument {
public getArgument(parameterName: string): FunctionCallArgument {
const arg = this.args.find((a) => a.parameterName === parameterName);
if (!arg) {
throw new Error(`no argument exists for parameter "${parameterName}"`);

View File

@@ -0,0 +1,10 @@
import type { FunctionCallArgumentFactory } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
import { FunctionCallArgumentStub } from './FunctionCallArgumentStub';
export class FunctionCallArgumentFactoryStub {
public factory: FunctionCallArgumentFactory = (parameterName, argumentValue) => {
return new FunctionCallArgumentStub()
.withParameterName(parameterName)
.withArgumentValue(argumentValue);
};
}

View File

@@ -1,6 +1,6 @@
import type { IFunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/IFunctionCallArgument';
import type { FunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
export class FunctionCallArgumentStub implements IFunctionCallArgument {
export class FunctionCallArgumentStub implements FunctionCallArgument {
public parameterName = 'stub-parameter-name';
public argumentValue = 'stub-arg-name';

View File

@@ -1,15 +1,15 @@
import type { IFunctionParameter } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/IFunctionParameter';
import type { IFunctionParameterCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
import type { FunctionParameter } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameter';
import { FunctionParameterStub } from './FunctionParameterStub';
export class FunctionParameterCollectionStub implements IFunctionParameterCollection {
private parameters = new Array<IFunctionParameter>();
private parameters = new Array<FunctionParameter>();
public addParameter(parameter: IFunctionParameter): void {
public addParameter(parameter: FunctionParameter): void {
this.parameters.push(parameter);
}
public get all(): readonly IFunctionParameter[] {
public get all(): readonly FunctionParameter[] {
return this.parameters;
}

View File

@@ -0,0 +1,8 @@
import type { FunctionParameterParser } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterParser';
import { FunctionParameterStub } from './FunctionParameterStub';
export const createFunctionParameterParserStub: FunctionParameterParser = (parameters) => {
return new FunctionParameterStub()
.withName(parameters.name)
.withOptional(parameters.optional || false);
};

View File

@@ -1,6 +1,6 @@
import type { IFunctionParameter } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/IFunctionParameter';
import type { FunctionParameter } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameter';
export class FunctionParameterStub implements IFunctionParameter {
export class FunctionParameterStub implements FunctionParameter {
public name = 'function-parameter-stub';
public isOptional = true;

View File

@@ -0,0 +1,12 @@
import type { ParameterNameValidator } from '@/application/Parser/Executable/Script/Compiler/Function/Shared/ParameterNameValidator';
export const createParameterNameValidatorStub = () => {
const validatedNames = new Array<string>();
const validator: ParameterNameValidator = (name) => {
validatedNames.push(name);
};
return {
validator,
validatedNames,
};
};

View File

@@ -1,4 +1,7 @@
import type { NonEmptyCollectionAssertion, ObjectAssertion, TypeValidator } from '@/application/Parser/Common/TypeValidator';
import type {
NonEmptyCollectionAssertion, NonEmptyStringAssertion,
ObjectAssertion, TypeValidator,
} from '@/application/Parser/Common/TypeValidator';
import type { FunctionKeys } from '@/TypeHelpers';
import { expectDeepIncludes } from '@tests/shared/Assertions/ExpectDeepIncludes';
import { StubWithObservableMethodCalls } from './StubWithObservableMethodCalls';
@@ -22,6 +25,13 @@ export class TypeValidatorStub
});
}
public assertNonEmptyString(assertion: NonEmptyStringAssertion): void {
this.registerMethodCall({
methodName: 'assertNonEmptyString',
args: [assertion],
});
}
public expectObjectAssertion<T>(
expectedAssertion: ObjectAssertion<T>,
): void {
@@ -34,6 +44,12 @@ export class TypeValidatorStub
this.expectAssertion('assertNonEmptyCollection', expectedAssertion);
}
public expectNonEmptyStringAssertion(
expectedAssertion: NonEmptyStringAssertion,
): void {
this.expectAssertion('assertNonEmptyString', expectedAssertion);
}
private expectAssertion<T extends FunctionKeys<TypeValidator>>(
methodName: T,
expectedAssertion: Parameters<TypeValidator[T]>[0],