Add type validation for parameters and fix types
This commit introduces type validation for parameter values within the parser/compiler, aligning with the YAML schema. It aims to eliminate dependencies on side effects in the collection files. This update changes the treatment of data types in the Windows collection, moving away from unintended type casting by the compiler. Previously, numeric and boolean values were used even though only string types were supported. This behavior was unstable and untested, and has now been adjusted to use strings exclusively. Changes ensure that parameter values are correctly validated as strings, enhancing stability and maintainability.
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
import type { PropertyKeys } from '@/TypeHelpers';
|
||||
import { isNullOrUndefined, isArray, isPlainObject } from '@/TypeHelpers';
|
||||
import {
|
||||
isNullOrUndefined, isArray, isPlainObject, isString,
|
||||
} from '@/TypeHelpers';
|
||||
|
||||
export interface TypeValidator {
|
||||
assertObject<T>(assertion: ObjectAssertion<T>): void;
|
||||
assertNonEmptyCollection(assertion: NonEmptyCollectionAssertion): void;
|
||||
assertNonEmptyString(assertion: NonEmptyStringAssertion): void;
|
||||
}
|
||||
|
||||
export interface NonEmptyCollectionAssertion {
|
||||
@@ -11,6 +14,17 @@ export interface NonEmptyCollectionAssertion {
|
||||
readonly valueName: string;
|
||||
}
|
||||
|
||||
export interface RegexValidationRule {
|
||||
readonly expectedMatch: RegExp;
|
||||
readonly errorMessage: string;
|
||||
}
|
||||
|
||||
export interface NonEmptyStringAssertion {
|
||||
readonly value: unknown;
|
||||
readonly valueName: string;
|
||||
readonly rule?: RegexValidationRule;
|
||||
}
|
||||
|
||||
export interface ObjectAssertion<T> {
|
||||
readonly value: T | unknown;
|
||||
readonly valueName: string;
|
||||
@@ -33,6 +47,18 @@ export function createTypeValidator(): TypeValidator {
|
||||
assertArray(assertion.value, assertion.valueName);
|
||||
assertNonEmpty(assertion.value, assertion.valueName);
|
||||
},
|
||||
assertNonEmptyString: (assertion) => {
|
||||
assertDefined(assertion.value, assertion.valueName);
|
||||
assertString(assertion.value, assertion.valueName);
|
||||
if (assertion.value.length === 0) {
|
||||
throw new Error(`'${assertion.valueName}' is missing.`);
|
||||
}
|
||||
if (assertion.rule) {
|
||||
if (!assertion.value.match(assertion.rule.expectedMatch)) {
|
||||
throw new Error(assertion.rule.errorMessage);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -86,6 +112,15 @@ function assertArray(
|
||||
}
|
||||
}
|
||||
|
||||
function assertString(
|
||||
value: unknown,
|
||||
valueName: string,
|
||||
): asserts value is string {
|
||||
if (!isString(value)) {
|
||||
throw new Error(`'${valueName}' should be of type 'string', but is of type '${typeof value}'.`);
|
||||
}
|
||||
}
|
||||
|
||||
function assertNonEmpty(
|
||||
value: Array<unknown>,
|
||||
valueName: string,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { IFunctionCallArgument } from './IFunctionCallArgument';
|
||||
import type { FunctionCallArgument } from './FunctionCallArgument';
|
||||
import type { IFunctionCallArgumentCollection } from './IFunctionCallArgumentCollection';
|
||||
|
||||
export class FunctionCallArgumentCollection implements IFunctionCallArgumentCollection {
|
||||
private readonly arguments = new Map<string, IFunctionCallArgument>();
|
||||
private readonly arguments = new Map<string, FunctionCallArgument>();
|
||||
|
||||
public addArgument(argument: IFunctionCallArgument): void {
|
||||
public addArgument(argument: FunctionCallArgument): void {
|
||||
if (this.hasArgument(argument.parameterName)) {
|
||||
throw new Error(`argument value for parameter ${argument.parameterName} is already provided`);
|
||||
}
|
||||
@@ -22,7 +22,7 @@ export class FunctionCallArgumentCollection implements IFunctionCallArgumentColl
|
||||
return this.arguments.has(parameterName);
|
||||
}
|
||||
|
||||
public getArgument(parameterName: string): IFunctionCallArgument {
|
||||
public getArgument(parameterName: string): FunctionCallArgument {
|
||||
if (!parameterName) {
|
||||
throw new Error('missing parameter name');
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
export interface IFunctionCallArgument {
|
||||
readonly parameterName: string;
|
||||
readonly argumentValue: string;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { IFunctionParameterCollection } from './IFunctionParameterCollection';
|
||||
import type { IFunctionParameter } from './IFunctionParameter';
|
||||
import type { FunctionParameter } from './FunctionParameter';
|
||||
|
||||
export class FunctionParameterCollection implements IFunctionParameterCollection {
|
||||
private parameters = new Array<IFunctionParameter>();
|
||||
private parameters = new Array<FunctionParameter>();
|
||||
|
||||
public get all(): readonly IFunctionParameter[] {
|
||||
public get all(): readonly FunctionParameter[] {
|
||||
return this.parameters;
|
||||
}
|
||||
|
||||
public addParameter(parameter: IFunctionParameter) {
|
||||
public addParameter(parameter: FunctionParameter) {
|
||||
this.ensureValidParameter(parameter);
|
||||
this.parameters.push(parameter);
|
||||
}
|
||||
@@ -17,7 +17,7 @@ export class FunctionParameterCollection implements IFunctionParameterCollection
|
||||
return this.parameters.find((existingParameter) => existingParameter.name === name);
|
||||
}
|
||||
|
||||
private ensureValidParameter(parameter: IFunctionParameter) {
|
||||
private ensureValidParameter(parameter: FunctionParameter) {
|
||||
if (this.includesName(parameter.name)) {
|
||||
throw new Error(`duplicate parameter name: "${parameter.name}"`);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
export interface IFunctionParameter {
|
||||
readonly name: string;
|
||||
readonly isOptional: boolean;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}".`,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -11,9 +11,10 @@ import { isArray, isNullOrUndefined, isPlainObject } from '@/TypeHelpers';
|
||||
import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/Common/ContextualError';
|
||||
import { createFunctionWithInlineCode, createCallerFunction } from './SharedFunction';
|
||||
import { SharedFunctionCollection } from './SharedFunctionCollection';
|
||||
import { FunctionParameter } from './Parameter/FunctionParameter';
|
||||
import { parseFunctionCalls, type FunctionCallsParser } from './Call/FunctionCallsParser';
|
||||
import { createFunctionParameterCollection, type FunctionParameterCollectionFactory } from './Parameter/FunctionParameterCollectionFactory';
|
||||
import { parseFunctionParameter, type FunctionParameterParser } from './Parameter/FunctionParameterParser';
|
||||
import type { FunctionParameter } from './Parameter/FunctionParameter';
|
||||
import type { ISharedFunctionCollection } from './ISharedFunctionCollection';
|
||||
import type { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
|
||||
import type { ISharedFunction } from './ISharedFunction';
|
||||
@@ -46,7 +47,7 @@ export const parseSharedFunctions: SharedFunctionsParser = (
|
||||
|
||||
const DefaultUtilities: SharedFunctionsParsingUtilities = {
|
||||
wrapError: wrapErrorWithAdditionalContext,
|
||||
createParameter: (...args) => new FunctionParameter(...args),
|
||||
parseParameter: parseFunctionParameter,
|
||||
codeValidator: CodeValidator.instance,
|
||||
createParameterCollection: createFunctionParameterCollection,
|
||||
parseFunctionCalls,
|
||||
@@ -54,16 +55,12 @@ const DefaultUtilities: SharedFunctionsParsingUtilities = {
|
||||
|
||||
interface SharedFunctionsParsingUtilities {
|
||||
readonly wrapError: ErrorWithContextWrapper;
|
||||
readonly createParameter: FunctionParameterFactory;
|
||||
readonly parseParameter: FunctionParameterParser;
|
||||
readonly codeValidator: ICodeValidator;
|
||||
readonly createParameterCollection: FunctionParameterCollectionFactory;
|
||||
readonly parseFunctionCalls: FunctionCallsParser;
|
||||
}
|
||||
|
||||
export type FunctionParameterFactory = (
|
||||
...args: ConstructorParameters<typeof FunctionParameter>
|
||||
) => FunctionParameter;
|
||||
|
||||
function parseFunction(
|
||||
data: FunctionData,
|
||||
syntax: ILanguageSyntax,
|
||||
@@ -100,7 +97,7 @@ function parseParameters(
|
||||
utilities: SharedFunctionsParsingUtilities,
|
||||
): IReadOnlyFunctionParameterCollection {
|
||||
return (data.parameters || [])
|
||||
.map((parameter) => createFunctionParameter(
|
||||
.map((parameter) => parseParameterWithContextualError(
|
||||
data.name,
|
||||
parameter,
|
||||
utilities,
|
||||
@@ -111,16 +108,13 @@ function parseParameters(
|
||||
}, utilities.createParameterCollection());
|
||||
}
|
||||
|
||||
function createFunctionParameter(
|
||||
function parseParameterWithContextualError(
|
||||
functionName: string,
|
||||
parameterData: ParameterDefinitionData,
|
||||
utilities: SharedFunctionsParsingUtilities,
|
||||
): FunctionParameter {
|
||||
try {
|
||||
return utilities.createParameter(
|
||||
parameterData.name,
|
||||
parameterData.optional || false,
|
||||
);
|
||||
return utilities.parseParameter(parameterData);
|
||||
} catch (err) {
|
||||
throw utilities.wrapError(
|
||||
err,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
|
||||
|
||||
export interface ICodeSubstituter {
|
||||
substitute(code: string, projectDetails: ProjectDetails): string;
|
||||
}
|
||||
@@ -5,8 +5,7 @@ import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
|
||||
import { createEnumParser, type EnumParser } from '../../Common/Enum';
|
||||
import { createTypeValidator, type TypeValidator } from '../Common/TypeValidator';
|
||||
import { CodeSubstituter } from './CodeSubstituter';
|
||||
import type { ICodeSubstituter } from './ICodeSubstituter';
|
||||
import { type CodeSubstituter, substituteCode } from './CodeSubstituter';
|
||||
|
||||
export const parseScriptingDefinition: ScriptingDefinitionParser = (
|
||||
definition,
|
||||
@@ -15,8 +14,8 @@ export const parseScriptingDefinition: ScriptingDefinitionParser = (
|
||||
) => {
|
||||
validateData(definition, utilities.validator);
|
||||
const language = utilities.languageParser.parseEnum(definition.language, 'language');
|
||||
const startCode = utilities.codeSubstituter.substitute(definition.startCode, projectDetails);
|
||||
const endCode = utilities.codeSubstituter.substitute(definition.endCode, projectDetails);
|
||||
const startCode = utilities.codeSubstituter(definition.startCode, projectDetails);
|
||||
const endCode = utilities.codeSubstituter(definition.endCode, projectDetails);
|
||||
return new ScriptingDefinition(
|
||||
language,
|
||||
startCode,
|
||||
@@ -45,12 +44,12 @@ function validateData(
|
||||
|
||||
interface ScriptingDefinitionParserUtilities {
|
||||
readonly languageParser: EnumParser<ScriptingLanguage>;
|
||||
readonly codeSubstituter: ICodeSubstituter;
|
||||
readonly codeSubstituter: CodeSubstituter;
|
||||
readonly validator: TypeValidator;
|
||||
}
|
||||
|
||||
const DefaultUtilities: ScriptingDefinitionParserUtilities = {
|
||||
languageParser: createEnumParser(ScriptingLanguage),
|
||||
codeSubstituter: new CodeSubstituter(),
|
||||
codeSubstituter: substituteCode,
|
||||
validator: createTypeValidator(),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user