Add more and unify tests for absent object cases
- Unify test data for nonexistence of an object/string and collection. - Introduce more test through adding missing test data to existing tests. - Improve logic for checking absence of values to match tests. - Add missing tests for absent value validation. - Update documentation to include shared test functionality.
This commit is contained in:
@@ -8,23 +8,25 @@ import { ExpressionEvaluationContext, IExpressionEvaluationContext } from './Exp
|
||||
|
||||
export type ExpressionEvaluator = (context: IExpressionEvaluationContext) => string;
|
||||
export class Expression implements IExpression {
|
||||
public readonly parameters: IReadOnlyFunctionParameterCollection;
|
||||
|
||||
constructor(
|
||||
public readonly position: ExpressionPosition,
|
||||
public readonly evaluator: ExpressionEvaluator,
|
||||
public readonly parameters
|
||||
: IReadOnlyFunctionParameterCollection = new FunctionParameterCollection(),
|
||||
parameters?: IReadOnlyFunctionParameterCollection,
|
||||
) {
|
||||
if (!position) {
|
||||
throw new Error('undefined position');
|
||||
throw new Error('missing position');
|
||||
}
|
||||
if (!evaluator) {
|
||||
throw new Error('undefined evaluator');
|
||||
throw new Error('missing evaluator');
|
||||
}
|
||||
this.parameters = parameters ?? new FunctionParameterCollection();
|
||||
}
|
||||
|
||||
public evaluate(context: IExpressionEvaluationContext): string {
|
||||
if (!context) {
|
||||
throw new Error('undefined context');
|
||||
throw new Error('missing context');
|
||||
}
|
||||
validateThatAllRequiredParametersAreSatisfied(this.parameters, context.args);
|
||||
const args = filterUnusedArguments(this.parameters, context.args);
|
||||
|
||||
@@ -13,7 +13,7 @@ export class ExpressionEvaluationContext implements IExpressionEvaluationContext
|
||||
public readonly pipelineCompiler: IPipelineCompiler = new PipelineCompiler(),
|
||||
) {
|
||||
if (!args) {
|
||||
throw new Error('undefined args, send empty collection instead');
|
||||
throw new Error('missing args, send empty collection instead.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export class ExpressionsCompiler implements IExpressionsCompiler {
|
||||
args: IReadOnlyFunctionCallArgumentCollection,
|
||||
): string {
|
||||
if (!args) {
|
||||
throw new Error('undefined args, send empty collection instead');
|
||||
throw new Error('missing args, send empty collection instead.');
|
||||
}
|
||||
if (!code) {
|
||||
return code;
|
||||
|
||||
@@ -10,8 +10,11 @@ const Parsers = [
|
||||
|
||||
export class CompositeExpressionParser implements IExpressionParser {
|
||||
public constructor(private readonly leafs: readonly IExpressionParser[] = Parsers) {
|
||||
if (!leafs) {
|
||||
throw new Error('missing leafs');
|
||||
}
|
||||
if (leafs.some((leaf) => !leaf)) {
|
||||
throw new Error('undefined leaf');
|
||||
throw new Error('missing leaf');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ export abstract class RegexParser implements IExpressionParser {
|
||||
|
||||
private* findRegexExpressions(code: string): Iterable<IExpression> {
|
||||
if (!code) {
|
||||
throw new Error('undefined code');
|
||||
throw new Error('missing code');
|
||||
}
|
||||
const matches = code.matchAll(this.regex);
|
||||
for (const match of matches) {
|
||||
|
||||
@@ -4,7 +4,10 @@ export class EscapeDoubleQuotes implements IPipe {
|
||||
public readonly name: string = 'escapeDoubleQuotes';
|
||||
|
||||
public apply(raw: string): string {
|
||||
return raw?.replaceAll('"', '"^""');
|
||||
if (!raw) {
|
||||
return raw;
|
||||
}
|
||||
return raw.replaceAll('"', '"^""');
|
||||
/* eslint-disable max-len */
|
||||
/*
|
||||
"^"" is the most robust and stable choice.
|
||||
|
||||
@@ -15,8 +15,11 @@ export class PipeFactory implements IPipeFactory {
|
||||
private readonly pipes = new Map<string, IPipe>();
|
||||
|
||||
constructor(pipes: readonly IPipe[] = RegisteredPipes) {
|
||||
if (!pipes) {
|
||||
throw new Error('missing pipes');
|
||||
}
|
||||
if (pipes.some((pipe) => !pipe)) {
|
||||
throw new Error('undefined pipe in list');
|
||||
throw new Error('missing pipe in list');
|
||||
}
|
||||
for (const pipe of pipes) {
|
||||
this.registerPipe(pipe);
|
||||
|
||||
@@ -23,8 +23,8 @@ function extractPipeNames(pipeline: string): string[] {
|
||||
}
|
||||
|
||||
function ensureValidArguments(value: string, pipeline: string) {
|
||||
if (!value) { throw new Error('undefined value'); }
|
||||
if (!pipeline) { throw new Error('undefined pipeline'); }
|
||||
if (!value) { throw new Error('missing value'); }
|
||||
if (!pipeline) { throw new Error('missing pipeline'); }
|
||||
if (!pipeline.trimStart().startsWith('|')) {
|
||||
throw new Error('pipeline does not start with pipe');
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ export class FunctionCallArgument implements IFunctionCallArgument {
|
||||
) {
|
||||
ensureValidParameterName(parameterName);
|
||||
if (!argumentValue) {
|
||||
throw new Error(`undefined argument value for "${parameterName}"`);
|
||||
throw new Error(`missing argument value for "${parameterName}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ export class FunctionCallArgumentCollection implements IFunctionCallArgumentColl
|
||||
|
||||
public addArgument(argument: IFunctionCallArgument): void {
|
||||
if (!argument) {
|
||||
throw new Error('undefined argument');
|
||||
throw new Error('missing argument');
|
||||
}
|
||||
if (this.hasArgument(argument.parameterName)) {
|
||||
throw new Error(`argument value for parameter ${argument.parameterName} is already provided`);
|
||||
@@ -20,14 +20,14 @@ export class FunctionCallArgumentCollection implements IFunctionCallArgumentColl
|
||||
|
||||
public hasArgument(parameterName: string): boolean {
|
||||
if (!parameterName) {
|
||||
throw new Error('undefined parameter name');
|
||||
throw new Error('missing parameter name');
|
||||
}
|
||||
return this.arguments.has(parameterName);
|
||||
}
|
||||
|
||||
public getArgument(parameterName: string): IFunctionCallArgument {
|
||||
if (!parameterName) {
|
||||
throw new Error('undefined parameter name');
|
||||
throw new Error('missing parameter name');
|
||||
}
|
||||
const arg = this.arguments.get(parameterName);
|
||||
if (!arg) {
|
||||
|
||||
@@ -22,9 +22,9 @@ export class FunctionCallCompiler implements IFunctionCallCompiler {
|
||||
calls: IFunctionCall[],
|
||||
functions: ISharedFunctionCollection,
|
||||
): ICompiledCode {
|
||||
if (!functions) { throw new Error('undefined functions'); }
|
||||
if (!calls) { throw new Error('undefined calls'); }
|
||||
if (calls.some((f) => !f)) { throw new Error('undefined function call'); }
|
||||
if (!functions) { throw new Error('missing functions'); }
|
||||
if (!calls) { throw new Error('missing calls'); }
|
||||
if (calls.some((f) => !f)) { throw new Error('missing function call'); }
|
||||
const context: ICompilationContext = {
|
||||
allFunctions: functions,
|
||||
callSequence: calls,
|
||||
|
||||
@@ -7,10 +7,10 @@ export class FunctionCall implements IFunctionCall {
|
||||
public readonly args: IReadOnlyFunctionCallArgumentCollection,
|
||||
) {
|
||||
if (!functionName) {
|
||||
throw new Error('empty function name in function call');
|
||||
throw new Error('missing function name in function call');
|
||||
}
|
||||
if (!args) {
|
||||
throw new Error('undefined args');
|
||||
throw new Error('missing args');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { FunctionCall } from './FunctionCall';
|
||||
|
||||
export function parseFunctionCalls(calls: FunctionCallsData): IFunctionCall[] {
|
||||
if (calls === undefined) {
|
||||
throw new Error('undefined call data');
|
||||
throw new Error('missing call data');
|
||||
}
|
||||
const sequence = getCallSequence(calls);
|
||||
return sequence.map((call) => parseFunctionCall(call));
|
||||
@@ -24,7 +24,7 @@ function getCallSequence(calls: FunctionCallsData): FunctionCallData[] {
|
||||
|
||||
function parseFunctionCall(call: FunctionCallData): IFunctionCall {
|
||||
if (!call) {
|
||||
throw new Error('undefined function call');
|
||||
throw new Error('missing call data');
|
||||
}
|
||||
const callArgs = parseArgs(call.parameters);
|
||||
return new FunctionCall(call.function, callArgs);
|
||||
|
||||
@@ -19,7 +19,7 @@ export class FunctionParameterCollection implements IFunctionParameterCollection
|
||||
|
||||
private ensureValidParameter(parameter: IFunctionParameter) {
|
||||
if (!parameter) {
|
||||
throw new Error('undefined parameter');
|
||||
throw new Error('missing parameter');
|
||||
}
|
||||
if (this.includesName(parameter.name)) {
|
||||
throw new Error(`duplicate parameter name: "${parameter.name}"`);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export function ensureValidParameterName(parameterName: string) {
|
||||
if (!parameterName) {
|
||||
throw new Error('undefined parameter name');
|
||||
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}"`);
|
||||
|
||||
@@ -9,11 +9,8 @@ export function createCallerFunction(
|
||||
parameters: IReadOnlyFunctionParameterCollection,
|
||||
callSequence: readonly IFunctionCall[],
|
||||
): ISharedFunction {
|
||||
if (!callSequence) {
|
||||
throw new Error(`undefined call sequence in function "${name}"`);
|
||||
}
|
||||
if (!callSequence.length) {
|
||||
throw new Error(`empty call sequence in function "${name}"`);
|
||||
if (!callSequence || !callSequence.length) {
|
||||
throw new Error(`missing call sequence in function "${name}"`);
|
||||
}
|
||||
return new SharedFunction(name, parameters, callSequence, FunctionBodyType.Calls);
|
||||
}
|
||||
@@ -43,8 +40,8 @@ class SharedFunction implements ISharedFunction {
|
||||
content: IFunctionCode | readonly IFunctionCall[],
|
||||
bodyType: FunctionBodyType,
|
||||
) {
|
||||
if (!name) { throw new Error('undefined function name'); }
|
||||
if (!parameters) { throw new Error('undefined parameters'); }
|
||||
if (!name) { throw new Error('missing function name'); }
|
||||
if (!parameters) { throw new Error('missing parameters'); }
|
||||
this.body = {
|
||||
type: bodyType,
|
||||
code: bodyType === FunctionBodyType.Code ? content as IFunctionCode : undefined,
|
||||
|
||||
@@ -5,7 +5,7 @@ export class SharedFunctionCollection implements ISharedFunctionCollection {
|
||||
private readonly functionsByName = new Map<string, ISharedFunction>();
|
||||
|
||||
public addFunction(func: ISharedFunction): void {
|
||||
if (!func) { throw new Error('undefined function'); }
|
||||
if (!func) { throw new Error('missing function'); }
|
||||
if (this.has(func.name)) {
|
||||
throw new Error(`function with name ${func.name} already exists`);
|
||||
}
|
||||
@@ -13,7 +13,7 @@ export class SharedFunctionCollection implements ISharedFunctionCollection {
|
||||
}
|
||||
|
||||
public getFunctionByName(name: string): ISharedFunction {
|
||||
if (!name) { throw Error('undefined function name'); }
|
||||
if (!name) { throw Error('missing function name'); }
|
||||
const func = this.functionsByName.get(name);
|
||||
if (!func) {
|
||||
throw new Error(`called function is not defined "${name}"`);
|
||||
|
||||
@@ -18,12 +18,12 @@ export class ScriptCompiler implements IScriptCompiler {
|
||||
private readonly callCompiler: IFunctionCallCompiler = FunctionCallCompiler.instance,
|
||||
sharedFunctionsParser: ISharedFunctionsParser = SharedFunctionsParser.instance,
|
||||
) {
|
||||
if (!syntax) { throw new Error('undefined syntax'); }
|
||||
if (!syntax) { throw new Error('missing syntax'); }
|
||||
this.functions = sharedFunctionsParser.parseFunctions(functions);
|
||||
}
|
||||
|
||||
public canCompile(script: ScriptData): boolean {
|
||||
if (!script) { throw new Error('undefined script'); }
|
||||
if (!script) { throw new Error('missing script'); }
|
||||
if (!script.call) {
|
||||
return false;
|
||||
}
|
||||
@@ -31,7 +31,7 @@ export class ScriptCompiler implements IScriptCompiler {
|
||||
}
|
||||
|
||||
public compile(script: ScriptData): IScriptCode {
|
||||
if (!script) { throw new Error('undefined script'); }
|
||||
if (!script) { throw new Error('missing script'); }
|
||||
try {
|
||||
const calls = parseFunctionCalls(script.call);
|
||||
const compiledCode = this.callCompiler.compileCall(calls, this.functions);
|
||||
|
||||
Reference in New Issue
Block a user