From fac26a6ca07479c84fe62c5ea2a572dad1898ef8 Mon Sep 17 00:00:00 2001 From: undergroundwires Date: Wed, 19 Jun 2024 17:01:27 +0200 Subject: [PATCH] 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. --- .../Parser/Common/TypeValidator.ts | 37 +++- .../Expressions/Parser/Regex/RegexParser.ts | 4 +- .../ParameterSubstitutionParser.ts | 6 +- .../Expressions/SyntaxParsers/WithParser.ts | 6 +- .../Call/Argument/FunctionCallArgument.ts | 51 +++-- .../FunctionCallArgumentCollection.ts | 8 +- .../Call/Argument/IFunctionCallArgument.ts | 4 - .../IFunctionCallArgumentCollection.ts | 6 +- .../NestedFunctionArgumentCompiler.ts | 22 +-- .../Function/Call/FunctionCallsParser.ts | 38 +++- .../Function/Parameter/FunctionParameter.ts | 13 +- .../Parameter/FunctionParameterCollection.ts | 10 +- .../Parameter/FunctionParameterParser.ts | 21 ++ .../Function/Parameter/IFunctionParameter.ts | 4 - .../Parameter/IFunctionParameterCollection.ts | 6 +- .../Function/Shared/ParameterNameValidator.ts | 28 ++- .../Function/SharedFunctionsParser.ts | 20 +- .../ScriptingDefinition/CodeSubstituter.ts | 56 ++++-- .../ScriptingDefinition/ICodeSubstituter.ts | 5 - .../ScriptingDefinitionParser.ts | 11 +- src/application/collections/windows.yaml | 180 +++++++++--------- .../Shared/ParameterNameValidator.spec.ts | 51 +++++ .../Parser/Common/TypeValidator.spec.ts | 106 ++++++++++- .../Argument/FunctionCallArgument.spec.ts | 118 +++++++++--- .../FunctionCallArgumentCollection.spec.ts | 4 +- .../NestedFunctionArgumentCompiler.spec.ts | 14 +- .../Function/Call/FunctionCallsParser.spec.ts | 18 +- .../Parameter/FunctionParameter.spec.ts | 50 ----- .../Parameter/FunctionParameterParser.spec.ts | 83 ++++++++ .../Function/SharedFunctionsParser.spec.ts | 22 +-- .../Compiler/ParameterNameTestRunner.ts | 54 ------ .../Compiler/ParameterNameValidator.spec.ts | 24 +++ .../CodeSubstituter.spec.ts | 159 +++++++++------- .../ScriptingDefinitionParser.spec.ts | 8 +- .../unit/shared/Stubs/CodeSubstituterStub.ts | 20 +- .../FunctionCallArgumentCollectionStub.ts | 8 +- .../Stubs/FunctionCallArgumentFactoryStub.ts | 10 + .../shared/Stubs/FunctionCallArgumentStub.ts | 4 +- .../Stubs/FunctionParameterCollectionStub.ts | 8 +- .../Stubs/FunctionParameterParserStub.ts | 8 + .../shared/Stubs/FunctionParameterStub.ts | 4 +- .../Stubs/ParameterNameValidatorStub.ts | 12 ++ tests/unit/shared/Stubs/TypeValidatorStub.ts | 18 +- 43 files changed, 873 insertions(+), 466 deletions(-) delete mode 100644 src/application/Parser/Executable/Script/Compiler/Function/Call/Argument/IFunctionCallArgument.ts create mode 100644 src/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterParser.ts delete mode 100644 src/application/Parser/Executable/Script/Compiler/Function/Parameter/IFunctionParameter.ts delete mode 100644 src/application/Parser/ScriptingDefinition/ICodeSubstituter.ts create mode 100644 tests/integration/application/Parser/Executable/Script/Compiler/Function/Shared/ParameterNameValidator.spec.ts delete mode 100644 tests/unit/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameter.spec.ts create mode 100644 tests/unit/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterParser.spec.ts delete mode 100644 tests/unit/application/Parser/Executable/Script/Compiler/ParameterNameTestRunner.ts create mode 100644 tests/unit/application/Parser/Executable/Script/Compiler/ParameterNameValidator.spec.ts create mode 100644 tests/unit/shared/Stubs/FunctionCallArgumentFactoryStub.ts create mode 100644 tests/unit/shared/Stubs/FunctionParameterParserStub.ts create mode 100644 tests/unit/shared/Stubs/ParameterNameValidatorStub.ts diff --git a/src/application/Parser/Common/TypeValidator.ts b/src/application/Parser/Common/TypeValidator.ts index 05d13efa..78f79cf5 100644 --- a/src/application/Parser/Common/TypeValidator.ts +++ b/src/application/Parser/Common/TypeValidator.ts @@ -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(assertion: ObjectAssertion): 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 { 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, valueName: string, diff --git a/src/application/Parser/Executable/Script/Compiler/Expressions/Parser/Regex/RegexParser.ts b/src/application/Parser/Executable/Script/Compiler/Expressions/Parser/Regex/RegexParser.ts index aeaa53c0..f3055bb2 100644 --- a/src/application/Parser/Executable/Script/Compiler/Expressions/Parser/Regex/RegexParser.ts +++ b/src/application/Parser/Executable/Script/Compiler/Expressions/Parser/Regex/RegexParser.ts @@ -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 { diff --git a/src/application/Parser/Executable/Script/Compiler/Expressions/SyntaxParsers/ParameterSubstitutionParser.ts b/src/application/Parser/Executable/Script/Compiler/Expressions/SyntaxParsers/ParameterSubstitutionParser.ts index c9900ca6..dd9da3af 100644 --- a/src/application/Parser/Executable/Script/Compiler/Expressions/SyntaxParsers/ParameterSubstitutionParser.ts +++ b/src/application/Parser/Executable/Script/Compiler/Expressions/SyntaxParsers/ParameterSubstitutionParser.ts @@ -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) { diff --git a/src/application/Parser/Executable/Script/Compiler/Expressions/SyntaxParsers/WithParser.ts b/src/application/Parser/Executable/Script/Compiler/Expressions/SyntaxParsers/WithParser.ts index d92244c2..19f251e3 100644 --- a/src/application/Parser/Executable/Script/Compiler/Expressions/SyntaxParsers/WithParser.ts +++ b/src/application/Parser/Executable/Script/Compiler/Expressions/SyntaxParsers/WithParser.ts @@ -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, diff --git a/src/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument.ts b/src/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument.ts index 59553003..4483f51c 100644 --- a/src/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument.ts +++ b/src/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument.ts @@ -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, +}; diff --git a/src/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection.ts b/src/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection.ts index e9fffd5a..e11fe90e 100644 --- a/src/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection.ts +++ b/src/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection.ts @@ -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(); + private readonly arguments = new Map(); - 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'); } diff --git a/src/application/Parser/Executable/Script/Compiler/Function/Call/Argument/IFunctionCallArgument.ts b/src/application/Parser/Executable/Script/Compiler/Function/Call/Argument/IFunctionCallArgument.ts deleted file mode 100644 index 99190d01..00000000 --- a/src/application/Parser/Executable/Script/Compiler/Function/Call/Argument/IFunctionCallArgument.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface IFunctionCallArgument { - readonly parameterName: string; - readonly argumentValue: string; -} diff --git a/src/application/Parser/Executable/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection.ts b/src/application/Parser/Executable/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection.ts index f3f76197..8ae2c938 100644 --- a/src/application/Parser/Executable/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection.ts +++ b/src/application/Parser/Executable/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection.ts @@ -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; } diff --git a/src/application/Parser/Executable/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/Argument/NestedFunctionArgumentCompiler.ts b/src/application/Parser/Executable/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/Argument/NestedFunctionArgumentCompiler.ts index f1ba6c04..20c77fbb 100644 --- a/src/application/Parser/Executable/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/Argument/NestedFunctionArgumentCompiler.ts +++ b/src/application/Parser/Executable/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/Argument/NestedFunctionArgumentCompiler.ts @@ -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, +}; diff --git a/src/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCallsParser.ts b/src/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCallsParser.ts index 3c8c54c4..d7dba7e3 100644 --- a/src/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCallsParser.ts +++ b/src/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCallsParser.ts @@ -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; diff --git a/src/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameter.ts b/src/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameter.ts index a2775c81..0529766a 100644 --- a/src/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameter.ts +++ b/src/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameter.ts @@ -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; } diff --git a/src/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterCollection.ts b/src/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterCollection.ts index ce3b9e5c..bae48614 100644 --- a/src/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterCollection.ts +++ b/src/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterCollection.ts @@ -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(); + private parameters = new Array(); - 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}"`); } diff --git a/src/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterParser.ts b/src/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterParser.ts new file mode 100644 index 00000000..0453a13a --- /dev/null +++ b/src/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterParser.ts @@ -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, + }; +}; diff --git a/src/application/Parser/Executable/Script/Compiler/Function/Parameter/IFunctionParameter.ts b/src/application/Parser/Executable/Script/Compiler/Function/Parameter/IFunctionParameter.ts deleted file mode 100644 index b012abed..00000000 --- a/src/application/Parser/Executable/Script/Compiler/Function/Parameter/IFunctionParameter.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface IFunctionParameter { - readonly name: string; - readonly isOptional: boolean; -} diff --git a/src/application/Parser/Executable/Script/Compiler/Function/Parameter/IFunctionParameterCollection.ts b/src/application/Parser/Executable/Script/Compiler/Function/Parameter/IFunctionParameterCollection.ts index c024d0ed..ecbe06b4 100644 --- a/src/application/Parser/Executable/Script/Compiler/Function/Parameter/IFunctionParameterCollection.ts +++ b/src/application/Parser/Executable/Script/Compiler/Function/Parameter/IFunctionParameterCollection.ts @@ -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; } diff --git a/src/application/Parser/Executable/Script/Compiler/Function/Shared/ParameterNameValidator.ts b/src/application/Parser/Executable/Script/Compiler/Function/Shared/ParameterNameValidator.ts index a5490bb3..48a3a2f3 100644 --- a/src/application/Parser/Executable/Script/Compiler/Function/Shared/ParameterNameValidator.ts +++ b/src/application/Parser/Executable/Script/Compiler/Function/Shared/ParameterNameValidator.ts @@ -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}".`, + }, + }); +}; diff --git a/src/application/Parser/Executable/Script/Compiler/Function/SharedFunctionsParser.ts b/src/application/Parser/Executable/Script/Compiler/Function/SharedFunctionsParser.ts index 60f9dde0..934b4cdf 100644 --- a/src/application/Parser/Executable/Script/Compiler/Function/SharedFunctionsParser.ts +++ b/src/application/Parser/Executable/Script/Compiler/Function/SharedFunctionsParser.ts @@ -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 -) => 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, diff --git a/src/application/Parser/ScriptingDefinition/CodeSubstituter.ts b/src/application/Parser/ScriptingDefinition/CodeSubstituter.ts index 52b3257b..20f52ec9 100644 --- a/src/application/Parser/ScriptingDefinition/CodeSubstituter.ts +++ b/src/application/Parser/ScriptingDefinition/CodeSubstituter.ts @@ -4,31 +4,33 @@ 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'; +import { createFunctionCallArgument, type FunctionCallArgumentFactory } from '../Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument'; -export class CodeSubstituter implements ICodeSubstituter { - constructor( - private readonly compiler: IExpressionsCompiler = createSubstituteCompiler(), - private readonly date = new Date(), - ) { - - } - - public substitute(code: string, projectDetails: ProjectDetails): string { - if (!code) { throw new Error('missing code'); } - const args = new FunctionCallArgumentCollection(); - const substitute = (name: string, value: string) => args - .addArgument(new FunctionCallArgument(name, value)); - substitute('homepage', projectDetails.homepage); - substitute('version', projectDetails.version.toString()); - substitute('date', this.date.toUTCString()); - const compiledCode = this.compiler.compileExpressions(code, args); - return compiledCode; - } +export interface CodeSubstituter { + ( + code: string, + projectDetails: ProjectDetails, + utilities?: CodeSubstitutionUtilities, + ): 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(utilities.createCallArgument(name, value)); + substitute('homepage', projectDetails.homepage); + substitute('version', projectDetails.version.toString()); + substitute('date', utilities.provideDate().toUTCString()); + const compiledCode = utilities.compiler.compileExpressions(code, args); + return compiledCode; +}; + function createSubstituteCompiler(): IExpressionsCompiler { const parsers: readonly IExpressionParser[] = [ new ParameterSubstitutionParser(), @@ -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, +}; diff --git a/src/application/Parser/ScriptingDefinition/ICodeSubstituter.ts b/src/application/Parser/ScriptingDefinition/ICodeSubstituter.ts deleted file mode 100644 index b3d624e9..00000000 --- a/src/application/Parser/ScriptingDefinition/ICodeSubstituter.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { ProjectDetails } from '@/domain/Project/ProjectDetails'; - -export interface ICodeSubstituter { - substitute(code: string, projectDetails: ProjectDetails): string; -} diff --git a/src/application/Parser/ScriptingDefinition/ScriptingDefinitionParser.ts b/src/application/Parser/ScriptingDefinition/ScriptingDefinitionParser.ts index 49ccf9f6..baf065c1 100644 --- a/src/application/Parser/ScriptingDefinition/ScriptingDefinitionParser.ts +++ b/src/application/Parser/ScriptingDefinition/ScriptingDefinitionParser.ts @@ -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; - readonly codeSubstituter: ICodeSubstituter; + readonly codeSubstituter: CodeSubstituter; readonly validator: TypeValidator; } const DefaultUtilities: ScriptingDefinitionParserUtilities = { languageParser: createEnumParser(ScriptingLanguage), - codeSubstituter: new CodeSubstituter(), + codeSubstituter: substituteCode, validator: createTypeValidator(), }; diff --git a/src/application/collections/windows.yaml b/src/application/collections/windows.yaml index fe11ebbd..57213eae 100644 --- a/src/application/collections/windows.yaml +++ b/src/application/collections/windows.yaml @@ -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: @@ -11935,10 +11935,10 @@ actions: - function: SetMpPreference parameters: - 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 + 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 - 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: diff --git a/tests/integration/application/Parser/Executable/Script/Compiler/Function/Shared/ParameterNameValidator.spec.ts b/tests/integration/application/Parser/Executable/Script/Compiler/Function/Shared/ParameterNameValidator.spec.ts new file mode 100644 index 00000000..4c8546d4 --- /dev/null +++ b/tests/integration/application/Parser/Executable/Script/Compiler/Function/Shared/ParameterNameValidator.spec.ts @@ -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(); + }); + }); + }); +}); diff --git a/tests/unit/application/Parser/Common/TypeValidator.spec.ts b/tests/unit/application/Parser/Common/TypeValidator.spec.ts index 4b8a273a..5bac11a2 100644 --- a/tests/unit/application/Parser/Common/TypeValidator.spec.ts +++ b/tests/unit/application/Parser/Common/TypeValidator.spec.ts @@ -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 { diff --git a/tests/unit/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument.spec.ts b/tests/unit/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument.spec.ts index bd3cb4b1..b35f6157 100644 --- a/tests/unit/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument.spec.ts +++ b/tests/unit/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument.spec.ts @@ -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 { + return createFunctionCallArgument( + this.parameterName, + this.argumentValue, + { + typeValidator: this.typeValidator, + validateParameterName: this.parameterNameValidator, + }, + ); } } diff --git a/tests/unit/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection.spec.ts b/tests/unit/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection.spec.ts index b29122ca..08c333bf 100644 --- a/tests/unit/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection.spec.ts +++ b/tests/unit/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection.spec.ts @@ -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', diff --git a/tests/unit/application/Parser/Executable/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/Argument/NestedFunctionArgumentCompiler.spec.ts b/tests/unit/application/Parser/Executable/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/Argument/NestedFunctionArgumentCompiler.spec.ts index edac5c28..868cb138 100644 --- a/tests/unit/application/Parser/Executable/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/Argument/NestedFunctionArgumentCompiler.spec.ts +++ b/tests/unit/application/Parser/Executable/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/Argument/NestedFunctionArgumentCompiler.spec.ts @@ -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, diff --git a/tests/unit/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCallsParser.spec.ts b/tests/unit/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCallsParser.spec.ts index 99ff12d1..1a318ef4 100644 --- a/tests/unit/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCallsParser.spec.ts +++ b/tests/unit/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCallsParser.spec.ts @@ -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 { return parseFunctionCalls( this.calls, - this.validator, + { + typeValidator: this.typeValidator, + createCallArgument: this.createCallArgument, + }, ); } } diff --git a/tests/unit/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameter.spec.ts b/tests/unit/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameter.spec.ts deleted file mode 100644 index 0d16dcd9..00000000 --- a/tests/unit/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameter.spec.ts +++ /dev/null @@ -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); - } -} diff --git a/tests/unit/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterParser.spec.ts b/tests/unit/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterParser.spec.ts new file mode 100644 index 00000000..fde3ee51 --- /dev/null +++ b/tests/unit/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterParser.spec.ts @@ -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, + ); + } +} diff --git a/tests/unit/application/Parser/Executable/Script/Compiler/Function/SharedFunctionsParser.spec.ts b/tests/unit/application/Parser/Executable/Script/Compiler/Function/SharedFunctionsParser.spec.ts index 47e1bbd2..adac3332 100644 --- a/tests/unit/application/Parser/Executable/Script/Compiler/Function/SharedFunctionsParser.spec.ts +++ b/tests/unit/application/Parser/Executable/Script/Compiler/Function/SharedFunctionsParser.spec.ts @@ -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, }, diff --git a/tests/unit/application/Parser/Executable/Script/Compiler/ParameterNameTestRunner.ts b/tests/unit/application/Parser/Executable/Script/Compiler/ParameterNameTestRunner.ts deleted file mode 100644 index d8216502..00000000 --- a/tests/unit/application/Parser/Executable/Script/Compiler/ParameterNameTestRunner.ts +++ /dev/null @@ -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); - }); - } - }); - }); -} diff --git a/tests/unit/application/Parser/Executable/Script/Compiler/ParameterNameValidator.spec.ts b/tests/unit/application/Parser/Executable/Script/Compiler/ParameterNameValidator.spec.ts new file mode 100644 index 00000000..7a1bcbe2 --- /dev/null +++ b/tests/unit/application/Parser/Executable/Script/Compiler/ParameterNameValidator.spec.ts @@ -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); + }); +}); diff --git a/tests/unit/application/Parser/ScriptingDefinition/CodeSubstituter.spec.ts b/tests/unit/application/Parser/ScriptingDefinition/CodeSubstituter.spec.ts index 337a5b55..ff6c5b30 100644 --- a/tests/unit/application/Parser/ScriptingDefinition/CodeSubstituter.spec.ts +++ b/tests/unit/application/Parser/ScriptingDefinition/CodeSubstituter.spec.ts @@ -1,91 +1,120 @@ 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('throws if code is empty', () => { - itEachAbsentStringValue((emptyCode) => { - // arrange - const expectedError = 'missing code'; - const code = emptyCode; - const projectDetails = new ProjectDetailsStub(); - const sut = new CodeSubstituterBuilder().build(); - // act - const act = () => sut.substitute(code, projectDetails); - // assert - expect(act).to.throw(expectedError); - }, { excludeNull: true, excludeUndefined: true }); - }); - describe('substitutes parameters as expected values', () => { - // arrange - const projectDetails = new ProjectDetailsStub(); - const date = new Date(); - const testCases: Array<{ parameter: string, argument: string }> = [ - { - parameter: 'homepage', - argument: projectDetails.homepage, - }, - { - parameter: 'version', - argument: projectDetails.version.toString(), - }, - { - parameter: 'date', - argument: date.toUTCString(), - }, - ]; - for (const testCase of testCases) { - it(`substitutes ${testCase.parameter} as expected`, () => { - const compilerStub = new ExpressionsCompilerStub(); - const sut = new CodeSubstituterBuilder() - .withCompiler(compilerStub) - .withDate(date) - .build(); + describe('substituteCode', () => { + describe('throws if code is empty', () => { + itEachAbsentStringValue((emptyCode) => { + // arrange + const expectedError = 'missing code'; + const context = new TestContext() + .withCode(emptyCode); // act - sut.substitute('non empty code', projectDetails); + const act = () => context.substitute(); // assert - expect(compilerStub.callHistory).to.have.lengthOf(1); - const parameters = compilerStub.callHistory[0].args[1]; - expect(parameters.hasArgument(testCase.parameter)); - const { argumentValue } = parameters.getArgument(testCase.parameter); - expect(argumentValue).to.equal(testCase.argument); - }); - } - }); - it('returns code as it is', () => { - // arrange - const expected = 'expected-code'; - const compilerStub = new ExpressionsCompilerStub(); - const sut = new CodeSubstituterBuilder() - .withCompiler(compilerStub) - .build(); - // act - sut.substitute(expected, new ProjectDetailsStub()); - // assert - expect(compilerStub.callHistory).to.have.lengthOf(1); - expect(compilerStub.callHistory[0].args[0]).to.equal(expected); + expect(act).to.throw(expectedError); + }, { excludeNull: true, excludeUndefined: true }); + }); + describe('substitutes parameters as expected values', () => { + // arrange + const projectDetails = new ProjectDetailsStub(); + const date = new Date(); + const testCases: Array<{ parameter: string, argument: string }> = [ + { + parameter: 'homepage', + argument: projectDetails.homepage, + }, + { + parameter: 'version', + argument: projectDetails.version.toString(), + }, + { + parameter: 'date', + argument: date.toUTCString(), + }, + ]; + for (const testCase of testCases) { + it(`substitutes ${testCase.parameter} as expected`, () => { + const compilerStub = new ExpressionsCompilerStub(); + const context = new TestContext() + .withCompiler(compilerStub) + .withDate(date) + .withProjectDetails(projectDetails); + // act + context.substitute(); + // assert + expect(compilerStub.callHistory).to.have.lengthOf(1); + const parameters = compilerStub.callHistory[0].args[1]; + expect(parameters.hasArgument(testCase.parameter)); + const { argumentValue } = parameters.getArgument(testCase.parameter); + expect(argumentValue).to.equal(testCase.argument); + }); + } + }); + it('returns code as it is', () => { + // arrange + const expected = 'expected-code'; + const compilerStub = new ExpressionsCompilerStub(); + const context = new TestContext() + .withCompiler(compilerStub) + .withCode(expected); + // act + 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 { + return substituteCode( + this.code, + this.projectDetails, + { + compiler: this.compiler, + provideDate: () => this.date, + createCallArgument: this.callArgumentFactory, + }, + ); } } diff --git a/tests/unit/application/Parser/ScriptingDefinition/ScriptingDefinitionParser.spec.ts b/tests/unit/application/Parser/ScriptingDefinition/ScriptingDefinitionParser.spec.ts index df35a4fa..e6ebea13 100644 --- a/tests/unit/application/Parser/ScriptingDefinition/ScriptingDefinitionParser.spec.ts +++ b/tests/unit/application/Parser/ScriptingDefinition/ScriptingDefinitionParser.spec.ts @@ -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 = new EnumParserStub() .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; } diff --git a/tests/unit/shared/Stubs/CodeSubstituterStub.ts b/tests/unit/shared/Stubs/CodeSubstituterStub.ts index 97425f5a..344bce22 100644 --- a/tests/unit/shared/Stubs/CodeSubstituterStub.ts +++ b/tests/unit/shared/Stubs/CodeSubstituterStub.ts @@ -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}`; + }; } diff --git a/tests/unit/shared/Stubs/FunctionCallArgumentCollectionStub.ts b/tests/unit/shared/Stubs/FunctionCallArgumentCollectionStub.ts index fd92c13e..d9bb9cd0 100644 --- a/tests/unit/shared/Stubs/FunctionCallArgumentCollectionStub.ts +++ b/tests/unit/shared/Stubs/FunctionCallArgumentCollectionStub.ts @@ -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(); + private args = new Array(); 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}"`); diff --git a/tests/unit/shared/Stubs/FunctionCallArgumentFactoryStub.ts b/tests/unit/shared/Stubs/FunctionCallArgumentFactoryStub.ts new file mode 100644 index 00000000..afb4da94 --- /dev/null +++ b/tests/unit/shared/Stubs/FunctionCallArgumentFactoryStub.ts @@ -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); + }; +} diff --git a/tests/unit/shared/Stubs/FunctionCallArgumentStub.ts b/tests/unit/shared/Stubs/FunctionCallArgumentStub.ts index 9a458a48..6634f5ca 100644 --- a/tests/unit/shared/Stubs/FunctionCallArgumentStub.ts +++ b/tests/unit/shared/Stubs/FunctionCallArgumentStub.ts @@ -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'; diff --git a/tests/unit/shared/Stubs/FunctionParameterCollectionStub.ts b/tests/unit/shared/Stubs/FunctionParameterCollectionStub.ts index 01f6cbe7..f9071a6b 100644 --- a/tests/unit/shared/Stubs/FunctionParameterCollectionStub.ts +++ b/tests/unit/shared/Stubs/FunctionParameterCollectionStub.ts @@ -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(); + private parameters = new Array(); - 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; } diff --git a/tests/unit/shared/Stubs/FunctionParameterParserStub.ts b/tests/unit/shared/Stubs/FunctionParameterParserStub.ts new file mode 100644 index 00000000..dd7c1df1 --- /dev/null +++ b/tests/unit/shared/Stubs/FunctionParameterParserStub.ts @@ -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); +}; diff --git a/tests/unit/shared/Stubs/FunctionParameterStub.ts b/tests/unit/shared/Stubs/FunctionParameterStub.ts index 8495aee7..ac5a8392 100644 --- a/tests/unit/shared/Stubs/FunctionParameterStub.ts +++ b/tests/unit/shared/Stubs/FunctionParameterStub.ts @@ -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; diff --git a/tests/unit/shared/Stubs/ParameterNameValidatorStub.ts b/tests/unit/shared/Stubs/ParameterNameValidatorStub.ts new file mode 100644 index 00000000..01f8a773 --- /dev/null +++ b/tests/unit/shared/Stubs/ParameterNameValidatorStub.ts @@ -0,0 +1,12 @@ +import type { ParameterNameValidator } from '@/application/Parser/Executable/Script/Compiler/Function/Shared/ParameterNameValidator'; + +export const createParameterNameValidatorStub = () => { + const validatedNames = new Array(); + const validator: ParameterNameValidator = (name) => { + validatedNames.push(name); + }; + return { + validator, + validatedNames, + }; +}; diff --git a/tests/unit/shared/Stubs/TypeValidatorStub.ts b/tests/unit/shared/Stubs/TypeValidatorStub.ts index b84ce673..da3b65e5 100644 --- a/tests/unit/shared/Stubs/TypeValidatorStub.ts +++ b/tests/unit/shared/Stubs/TypeValidatorStub.ts @@ -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( expectedAssertion: ObjectAssertion, ): void { @@ -34,6 +44,12 @@ export class TypeValidatorStub this.expectAssertion('assertNonEmptyCollection', expectedAssertion); } + public expectNonEmptyStringAssertion( + expectedAssertion: NonEmptyStringAssertion, + ): void { + this.expectAssertion('assertNonEmptyString', expectedAssertion); + } + private expectAssertion>( methodName: T, expectedAssertion: Parameters[0],