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:
undergroundwires
2022-01-21 22:34:11 +01:00
parent 0e52a99efa
commit 44d79e2c9a
100 changed files with 1380 additions and 976 deletions

View File

@@ -17,7 +17,7 @@ export class CategoryCollectionParseContext implements ICategoryCollectionParseC
scripting: IScriptingDefinition,
syntaxFactory: ISyntaxFactory = new SyntaxFactory(),
) {
if (!scripting) { throw new Error('undefined scripting'); }
if (!scripting) { throw new Error('missing scripting'); }
this.syntax = syntaxFactory.create(scripting.language);
this.compiler = new ScriptCompiler(functionsData, this.syntax);
}

View File

@@ -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);

View File

@@ -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.');
}
}
}

View File

@@ -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;

View File

@@ -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');
}
}

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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);

View File

@@ -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');
}

View File

@@ -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}"`);
}
}
}

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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');
}
}
}

View File

@@ -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);

View File

@@ -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}"`);

View File

@@ -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}"`);

View File

@@ -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,

View File

@@ -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}"`);

View File

@@ -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);

View File

@@ -13,7 +13,7 @@ export function parseScript(
levelParser = createEnumParser(RecommendationLevel),
): Script {
validateScript(data);
if (!context) { throw new Error('undefined context'); }
if (!context) { throw new Error('missing context'); }
const script = new Script(
/* name: */ data.name,
/* code: */ parseCode(data, context),
@@ -51,7 +51,7 @@ function ensureNotBothCallAndCode(script: ScriptData) {
function validateScript(script: ScriptData) {
if (!script) {
throw new Error('undefined script');
throw new Error('missing script');
}
if (!script.code && !script.call) {
throw new Error('must define either "call" or "code"');