Add optionality for parameters
This commit allows for parameters that does not require any arguments to be provided in function calls. It changes collection syntax where parameters are list of objects instead of primitive strings. A parameter has now 'name' and 'optional' properties. 'name' is required and used in same way as older strings as parameter definitions. 'Optional' property is optional, 'false' is the default behavior if undefined. It also adds additional validation to restrict parameter names to alphanumeric strings to have a clear syntax in expressions.
This commit is contained in:
@@ -115,7 +115,8 @@ A simple function example
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
function: EchoArgument
|
function: EchoArgument
|
||||||
parameters: [ 'argument' ]
|
parameters:
|
||||||
|
- name: 'argument'
|
||||||
code: Hello {{ $argument }} !
|
code: Hello {{ $argument }} !
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -134,14 +135,16 @@ A function can call other functions such as:
|
|||||||
```yaml
|
```yaml
|
||||||
-
|
-
|
||||||
function: CallerFunction
|
function: CallerFunction
|
||||||
parameters: [ 'value' ]
|
parameters:
|
||||||
|
- name: 'value'
|
||||||
call:
|
call:
|
||||||
function: EchoArgument
|
function: EchoArgument
|
||||||
parameters:
|
parameters:
|
||||||
argument: {{ $value }}
|
argument: {{ $value }}
|
||||||
-
|
-
|
||||||
function: EchoArgument
|
function: EchoArgument
|
||||||
parameters: [ 'argument' ]
|
parameters:
|
||||||
|
- name: 'argument'
|
||||||
code: Hello {{ $argument }} !
|
code: Hello {{ $argument }} !
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -152,11 +155,9 @@ A function can call other functions such as:
|
|||||||
- Convention is to use camelCase, and be verbs.
|
- Convention is to use camelCase, and be verbs.
|
||||||
- E.g. `uninstallStoreApp`
|
- E.g. `uninstallStoreApp`
|
||||||
- ❗ Function names must be unique
|
- ❗ Function names must be unique
|
||||||
- `parameters`: `[` *`string`* `, ... ]`
|
- `parameters`: `[` ***[`FunctionParameter`](#FunctionParameter)*** `, ... ]`
|
||||||
- Name of the parameters that the function has.
|
- List of parameters that function code refers to.
|
||||||
- Parameter values are provided by a [Script](#script) through a [FunctionCall](#FunctionCall)
|
- ❗ Must be defined to be able use in [`FunctionCall`](#functioncall) or [expressions](#expressions)
|
||||||
- Parameter names must be defined to be used in [expressions](#expressions)
|
|
||||||
- ❗ Parameter names must be unique
|
|
||||||
`code`: *`string`* (**required** if `call` is undefined)
|
`code`: *`string`* (**required** if `call` is undefined)
|
||||||
- Batch file commands that will be executed
|
- Batch file commands that will be executed
|
||||||
- 💡 If defined, best practice to also define `revertCode`
|
- 💡 If defined, best practice to also define `revertCode`
|
||||||
@@ -170,6 +171,24 @@ A function can call other functions such as:
|
|||||||
- The parameter values that are sent can use [expressions](#expressions)
|
- The parameter values that are sent can use [expressions](#expressions)
|
||||||
- ❗ If not defined `code` must be defined
|
- ❗ If not defined `code` must be defined
|
||||||
|
|
||||||
|
### `FunctionParameter`
|
||||||
|
|
||||||
|
- Defines a parameter that function requires optionally or mandatory.
|
||||||
|
- Its arguments are provided by a [Script](#script) through a [FunctionCall](#FunctionCall).
|
||||||
|
|
||||||
|
#### `FunctionParameter` syntax
|
||||||
|
|
||||||
|
- `name`: *`string`* (**required**)
|
||||||
|
- Name of the parameters that the function has.
|
||||||
|
- Parameter names must be defined to be used in [expressions](#expressions).
|
||||||
|
- ❗ Parameter names must be unique and include alphanumeric characters only.
|
||||||
|
- `optional`: *`boolean`* (default: `false`)
|
||||||
|
- Specifies whether the caller [Script](#script) must provide any value for the parameter.
|
||||||
|
- If set to `false` i.e. an argument value is not optional then it expects a non-empty value for the variable;
|
||||||
|
- Otherwise it throws.
|
||||||
|
- 💡 Set it to `true` if a parameter is used conditionally;
|
||||||
|
- Or else set it to `false` for verbosity or do not define it as default value is `false` anyway.
|
||||||
|
|
||||||
### `ScriptingDefinition`
|
### `ScriptingDefinition`
|
||||||
|
|
||||||
- Defines global properties for scripting that's used throughout its parent [Collection](#collection).
|
- Defines global properties for scripting that's used throughout its parent [Collection](#collection).
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import { ExpressionPosition } from './ExpressionPosition';
|
import { ExpressionPosition } from './ExpressionPosition';
|
||||||
import { ExpressionArguments, IExpression } from './IExpression';
|
import { IExpression } from './IExpression';
|
||||||
|
import { IReadOnlyFunctionCallArgumentCollection } from '../../FunctionCall/Argument/IFunctionCallArgumentCollection';
|
||||||
|
import { IReadOnlyFunctionParameterCollection } from '../../Function/Parameter/IFunctionParameterCollection';
|
||||||
|
import { FunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection';
|
||||||
|
import { FunctionCallArgumentCollection } from '../../FunctionCall/Argument/FunctionCallArgumentCollection';
|
||||||
|
|
||||||
export type ExpressionEvaluator = (args?: ExpressionArguments) => string;
|
export type ExpressionEvaluator = (args: IReadOnlyFunctionCallArgumentCollection) => string;
|
||||||
export class Expression implements IExpression {
|
export class Expression implements IExpression {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly position: ExpressionPosition,
|
public readonly position: ExpressionPosition,
|
||||||
public readonly evaluator: ExpressionEvaluator,
|
public readonly evaluator: ExpressionEvaluator,
|
||||||
public readonly parameters: readonly string[] = new Array<string>()) {
|
public readonly parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollection()) {
|
||||||
if (!position) {
|
if (!position) {
|
||||||
throw new Error('undefined position');
|
throw new Error('undefined position');
|
||||||
}
|
}
|
||||||
@@ -14,22 +18,42 @@ export class Expression implements IExpression {
|
|||||||
throw new Error('undefined evaluator');
|
throw new Error('undefined evaluator');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public evaluate(args?: ExpressionArguments): string {
|
public evaluate(args: IReadOnlyFunctionCallArgumentCollection): string {
|
||||||
|
if (!args) {
|
||||||
|
throw new Error('undefined args, send empty collection instead');
|
||||||
|
}
|
||||||
|
validateThatAllRequiredParametersAreSatisfied(this.parameters, args);
|
||||||
args = filterUnusedArguments(this.parameters, args);
|
args = filterUnusedArguments(this.parameters, args);
|
||||||
return this.evaluator(args);
|
return this.evaluator(args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterUnusedArguments(
|
function validateThatAllRequiredParametersAreSatisfied(
|
||||||
parameters: readonly string[], args: ExpressionArguments): ExpressionArguments {
|
parameters: IReadOnlyFunctionParameterCollection,
|
||||||
let result: ExpressionArguments = {};
|
args: IReadOnlyFunctionCallArgumentCollection,
|
||||||
for (const parameter of Object.keys(args)) {
|
) {
|
||||||
if (parameters.includes(parameter)) {
|
const requiredParameterNames = parameters
|
||||||
result = {
|
.all
|
||||||
...result,
|
.filter((parameter) => !parameter.isOptional)
|
||||||
[parameter]: args[parameter],
|
.map((parameter) => parameter.name);
|
||||||
};
|
const missingParameterNames = requiredParameterNames
|
||||||
|
.filter((parameterName) => !args.hasArgument(parameterName));
|
||||||
|
if (missingParameterNames.length) {
|
||||||
|
throw new Error(
|
||||||
|
`argument values are provided for required parameters: "${missingParameterNames.join('", "')}"`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
|
function filterUnusedArguments(
|
||||||
|
parameters: IReadOnlyFunctionParameterCollection,
|
||||||
|
allFunctionArgs: IReadOnlyFunctionCallArgumentCollection): IReadOnlyFunctionCallArgumentCollection {
|
||||||
|
const specificCallArgs = new FunctionCallArgumentCollection();
|
||||||
|
for (const parameter of parameters.all) {
|
||||||
|
if (parameter.isOptional && !allFunctionArgs.hasArgument(parameter.name)) {
|
||||||
|
continue; // Optional parameter is not necessarily provided
|
||||||
|
}
|
||||||
|
const arg = allFunctionArgs.getArgument(parameter.name);
|
||||||
|
specificCallArgs.addArgument(arg);
|
||||||
|
}
|
||||||
|
return specificCallArgs;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import { ExpressionPosition } from './ExpressionPosition';
|
import { ExpressionPosition } from './ExpressionPosition';
|
||||||
|
import { IReadOnlyFunctionCallArgumentCollection } from '../../FunctionCall/Argument/IFunctionCallArgumentCollection';
|
||||||
|
import { IReadOnlyFunctionParameterCollection } from '../../Function/Parameter/IFunctionParameterCollection';
|
||||||
|
|
||||||
export interface IExpression {
|
export interface IExpression {
|
||||||
readonly position: ExpressionPosition;
|
readonly position: ExpressionPosition;
|
||||||
readonly parameters?: readonly string[];
|
readonly parameters: IReadOnlyFunctionParameterCollection;
|
||||||
evaluate(args?: ExpressionArguments): string;
|
evaluate(args: IReadOnlyFunctionCallArgumentCollection): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExpressionArguments {
|
|
||||||
readonly [parameter: string]: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,39 @@
|
|||||||
import { IExpressionsCompiler, ParameterValueDictionary } from './IExpressionsCompiler';
|
import { IExpressionsCompiler } from './IExpressionsCompiler';
|
||||||
import { IExpression } from './Expression/IExpression';
|
import { IExpression } from './Expression/IExpression';
|
||||||
import { IExpressionParser } from './Parser/IExpressionParser';
|
import { IExpressionParser } from './Parser/IExpressionParser';
|
||||||
import { CompositeExpressionParser } from './Parser/CompositeExpressionParser';
|
import { CompositeExpressionParser } from './Parser/CompositeExpressionParser';
|
||||||
|
import { IReadOnlyFunctionCallArgumentCollection } from '../FunctionCall/Argument/IFunctionCallArgumentCollection';
|
||||||
|
|
||||||
export class ExpressionsCompiler implements IExpressionsCompiler {
|
export class ExpressionsCompiler implements IExpressionsCompiler {
|
||||||
public constructor(private readonly extractor: IExpressionParser = new CompositeExpressionParser()) { }
|
public constructor(
|
||||||
public compileExpressions(code: string, parameters?: ParameterValueDictionary): string {
|
private readonly extractor: IExpressionParser = new CompositeExpressionParser()) { }
|
||||||
|
public compileExpressions(
|
||||||
|
code: string,
|
||||||
|
args: IReadOnlyFunctionCallArgumentCollection): string {
|
||||||
|
if (!args) {
|
||||||
|
throw new Error('undefined args, send empty collection instead');
|
||||||
|
}
|
||||||
const expressions = this.extractor.findExpressions(code);
|
const expressions = this.extractor.findExpressions(code);
|
||||||
const requiredParameterNames = expressions.map((e) => e.parameters).filter((p) => p).flat();
|
ensureParamsUsedInCodeHasArgsProvided(expressions, args);
|
||||||
const uniqueParameterNames = Array.from(new Set(requiredParameterNames));
|
const compiledCode = compileExpressions(expressions, code, args);
|
||||||
ensureRequiredArgsProvided(uniqueParameterNames, parameters);
|
return compiledCode;
|
||||||
return compileExpressions(expressions, code, parameters);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function compileExpressions(expressions: IExpression[], code: string, parameters?: ParameterValueDictionary) {
|
function compileExpressions(
|
||||||
|
expressions: readonly IExpression[],
|
||||||
|
code: string,
|
||||||
|
args: IReadOnlyFunctionCallArgumentCollection): string {
|
||||||
let compiledCode = '';
|
let compiledCode = '';
|
||||||
expressions = expressions
|
const sortedExpressions = expressions
|
||||||
.slice() // copy the array to not mutate the parameter
|
.slice() // copy the array to not mutate the parameter
|
||||||
.sort((a, b) => b.position.start - a.position.start);
|
.sort((a, b) => b.position.start - a.position.start);
|
||||||
let index = 0;
|
let index = 0;
|
||||||
while (index !== code.length) {
|
while (index !== code.length) {
|
||||||
const nextExpression = expressions.pop();
|
const nextExpression = sortedExpressions.pop();
|
||||||
if (nextExpression) {
|
if (nextExpression) {
|
||||||
compiledCode += code.substring(index, nextExpression.position.start);
|
compiledCode += code.substring(index, nextExpression.position.start);
|
||||||
const expressionCode = nextExpression.evaluate(parameters);
|
const expressionCode = nextExpression.evaluate(args);
|
||||||
compiledCode += expressionCode;
|
compiledCode += expressionCode;
|
||||||
index = nextExpression.position.end;
|
index = nextExpression.position.end;
|
||||||
} else {
|
} else {
|
||||||
@@ -35,15 +44,29 @@ function compileExpressions(expressions: IExpression[], code: string, parameters
|
|||||||
return compiledCode;
|
return compiledCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureRequiredArgsProvided(parameters: readonly string[], args: ParameterValueDictionary) {
|
function extractRequiredParameterNames(
|
||||||
parameters = parameters || [];
|
expressions: readonly IExpression[]): string[] {
|
||||||
args = args || {};
|
const usedParameterNames = expressions
|
||||||
if (!parameters.length) {
|
.map((e) => e.parameters.all
|
||||||
|
.filter((p) => !p.isOptional)
|
||||||
|
.map((p) => p.name))
|
||||||
|
.filter((p) => p)
|
||||||
|
.flat();
|
||||||
|
const uniqueParameterNames = Array.from(new Set(usedParameterNames));
|
||||||
|
return uniqueParameterNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureParamsUsedInCodeHasArgsProvided(
|
||||||
|
expressions: readonly IExpression[],
|
||||||
|
providedArgs: IReadOnlyFunctionCallArgumentCollection): void {
|
||||||
|
const usedParameterNames = extractRequiredParameterNames(expressions);
|
||||||
|
if (!usedParameterNames?.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const notProvidedParameters = parameters.filter((parameter) => !Boolean(args[parameter]));
|
const notProvidedParameters = usedParameterNames
|
||||||
|
.filter((parameterName) => !providedArgs.hasArgument(parameterName));
|
||||||
if (notProvidedParameters.length) {
|
if (notProvidedParameters.length) {
|
||||||
throw new Error(`parameter value(s) not provided for: ${printList(notProvidedParameters)}`);
|
throw new Error(`parameter value(s) not provided for: ${printList(notProvidedParameters)} but used in code`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
export interface ParameterValueDictionary { [parameterName: string]: string; }
|
import { IReadOnlyFunctionCallArgumentCollection } from '../FunctionCall/Argument/IFunctionCallArgumentCollection';
|
||||||
|
|
||||||
export interface IExpressionsCompiler {
|
export interface IExpressionsCompiler {
|
||||||
compileExpressions(code: string, parameters?: ParameterValueDictionary): string;
|
compileExpressions(
|
||||||
|
code: string,
|
||||||
|
args: IReadOnlyFunctionCallArgumentCollection): string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,15 @@ import { IExpression } from '../Expression/IExpression';
|
|||||||
import { IExpressionParser } from './IExpressionParser';
|
import { IExpressionParser } from './IExpressionParser';
|
||||||
import { ParameterSubstitutionParser } from '../SyntaxParsers/ParameterSubstitutionParser';
|
import { ParameterSubstitutionParser } from '../SyntaxParsers/ParameterSubstitutionParser';
|
||||||
|
|
||||||
const parsers = [
|
const Parsers = [
|
||||||
new ParameterSubstitutionParser(),
|
new ParameterSubstitutionParser(),
|
||||||
];
|
];
|
||||||
|
|
||||||
export class CompositeExpressionParser implements IExpressionParser {
|
export class CompositeExpressionParser implements IExpressionParser {
|
||||||
public constructor(private readonly leafs: readonly IExpressionParser[] = parsers) {
|
public constructor(private readonly leafs: readonly IExpressionParser[] = Parsers) {
|
||||||
if (leafs.some((leaf) => !leaf)) { throw new Error('undefined leaf'); }
|
if (leafs.some((leaf) => !leaf)) {
|
||||||
|
throw new Error('undefined leaf');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public findExpressions(code: string): IExpression[] {
|
public findExpressions(code: string): IExpression[] {
|
||||||
const expressions = new Array<IExpression>();
|
const expressions = new Array<IExpression>();
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ import { IExpressionParser } from './IExpressionParser';
|
|||||||
import { ExpressionPosition } from '../Expression/ExpressionPosition';
|
import { ExpressionPosition } from '../Expression/ExpressionPosition';
|
||||||
import { IExpression } from '../Expression/IExpression';
|
import { IExpression } from '../Expression/IExpression';
|
||||||
import { Expression, ExpressionEvaluator } from '../Expression/Expression';
|
import { Expression, ExpressionEvaluator } from '../Expression/Expression';
|
||||||
|
import { IFunctionParameter } from '../../Function/Parameter/IFunctionParameter';
|
||||||
|
import { FunctionParameterCollection } from '../../Function/Parameter/FunctionParameterCollection';
|
||||||
|
|
||||||
export abstract class RegexParser implements IExpressionParser {
|
export abstract class RegexParser implements IExpressionParser {
|
||||||
protected abstract readonly regex: RegExp;
|
protected abstract readonly regex: RegExp;
|
||||||
|
|
||||||
public findExpressions(code: string): IExpression[] {
|
public findExpressions(code: string): IExpression[] {
|
||||||
return Array.from(this.findRegexExpressions(code));
|
return Array.from(this.findRegexExpressions(code));
|
||||||
}
|
}
|
||||||
@@ -23,7 +26,8 @@ export abstract class RegexParser implements IExpressionParser {
|
|||||||
throw new Error(`[${this.constructor.name}] invalid script position: ${error.message}\nRegex ${this.regex}\nCode: ${code}`);
|
throw new Error(`[${this.constructor.name}] invalid script position: ${error.message}\nRegex ${this.regex}\nCode: ${code}`);
|
||||||
}
|
}
|
||||||
const primitiveExpression = this.buildExpression(match);
|
const primitiveExpression = this.buildExpression(match);
|
||||||
const expression = new Expression(position, primitiveExpression.evaluator, primitiveExpression.parameters);
|
const parameters = getParameters(primitiveExpression);
|
||||||
|
const expression = new Expression(position, primitiveExpression.evaluator, parameters);
|
||||||
yield expression;
|
yield expression;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -31,5 +35,14 @@ export abstract class RegexParser implements IExpressionParser {
|
|||||||
|
|
||||||
export interface IPrimitiveExpression {
|
export interface IPrimitiveExpression {
|
||||||
evaluator: ExpressionEvaluator;
|
evaluator: ExpressionEvaluator;
|
||||||
parameters?: readonly string[];
|
parameters?: readonly IFunctionParameter[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getParameters(
|
||||||
|
expression: IPrimitiveExpression): FunctionParameterCollection {
|
||||||
|
const parameters = new FunctionParameterCollection();
|
||||||
|
for (const parameter of expression.parameters || []) {
|
||||||
|
parameters.addParameter(parameter);
|
||||||
|
}
|
||||||
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { RegexParser, IPrimitiveExpression } from '../Parser/RegexParser';
|
import { RegexParser, IPrimitiveExpression } from '../Parser/RegexParser';
|
||||||
|
import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameter';
|
||||||
|
|
||||||
export class ParameterSubstitutionParser extends RegexParser {
|
export class ParameterSubstitutionParser extends RegexParser {
|
||||||
protected readonly regex = /{{\s*\$([^}| ]+)\s*}}/g;
|
protected readonly regex = /{{\s*\$([^}| ]+)\s*}}/g;
|
||||||
protected buildExpression(match: RegExpMatchArray): IPrimitiveExpression {
|
protected buildExpression(match: RegExpMatchArray): IPrimitiveExpression {
|
||||||
const parameterName = match[1];
|
const parameterName = match[1];
|
||||||
return {
|
return {
|
||||||
parameters: [ parameterName ],
|
parameters: [ new FunctionParameter(parameterName, false) ],
|
||||||
evaluator: (args) => args[parameterName],
|
evaluator: (args) => args.getArgument(parameterName).argumentValue,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import { ISharedFunctionCollection } from './ISharedFunctionCollection';
|
|||||||
import { IFunctionCompiler } from './IFunctionCompiler';
|
import { IFunctionCompiler } from './IFunctionCompiler';
|
||||||
import { IFunctionCallCompiler } from '../FunctionCall/IFunctionCallCompiler';
|
import { IFunctionCallCompiler } from '../FunctionCall/IFunctionCallCompiler';
|
||||||
import { FunctionCallCompiler } from '../FunctionCall/FunctionCallCompiler';
|
import { FunctionCallCompiler } from '../FunctionCall/FunctionCallCompiler';
|
||||||
|
import { FunctionParameter } from './Parameter/FunctionParameter';
|
||||||
|
import { FunctionParameterCollection } from './Parameter/FunctionParameterCollection';
|
||||||
|
import { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
|
||||||
|
|
||||||
export class FunctionCompiler implements IFunctionCompiler {
|
export class FunctionCompiler implements IFunctionCompiler {
|
||||||
public static readonly instance: IFunctionCompiler = new FunctionCompiler();
|
public static readonly instance: IFunctionCompiler = new FunctionCompiler();
|
||||||
@@ -20,20 +23,39 @@ export class FunctionCompiler implements IFunctionCompiler {
|
|||||||
functions
|
functions
|
||||||
.filter((func) => hasCode(func))
|
.filter((func) => hasCode(func))
|
||||||
.forEach((func) => {
|
.forEach((func) => {
|
||||||
const shared = new SharedFunction(func.name, func.parameters, func.code, func.revertCode);
|
const parameters = parseParameters(func);
|
||||||
|
const shared = new SharedFunction(func.name, parameters, func.code, func.revertCode);
|
||||||
collection.addFunction(shared);
|
collection.addFunction(shared);
|
||||||
});
|
});
|
||||||
functions
|
functions
|
||||||
.filter((func) => hasCall(func))
|
.filter((func) => hasCall(func))
|
||||||
.forEach((func) => {
|
.forEach((func) => {
|
||||||
|
const parameters = parseParameters(func);
|
||||||
const code = this.functionCallCompiler.compileCall(func.call, collection);
|
const code = this.functionCallCompiler.compileCall(func.call, collection);
|
||||||
const shared = new SharedFunction(func.name, func.parameters, code.code, code.revertCode);
|
const shared = new SharedFunction(func.name, parameters, code.code, code.revertCode);
|
||||||
collection.addFunction(shared);
|
collection.addFunction(shared);
|
||||||
});
|
});
|
||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseParameters(data: FunctionData): IReadOnlyFunctionParameterCollection {
|
||||||
|
const parameters = new FunctionParameterCollection();
|
||||||
|
if (!data.parameters) {
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
for (const parameterData of data.parameters) {
|
||||||
|
const isOptional = parameterData.optional || false;
|
||||||
|
try {
|
||||||
|
const parameter = new FunctionParameter(parameterData.name, isOptional);
|
||||||
|
parameters.addParameter(parameter);
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`"${data.name}": ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
function hasCode(data: FunctionData): boolean {
|
function hasCode(data: FunctionData): boolean {
|
||||||
return Boolean(data.code);
|
return Boolean(data.code);
|
||||||
}
|
}
|
||||||
@@ -46,10 +68,9 @@ function hasCall(data: FunctionData): boolean {
|
|||||||
function ensureValidFunctions(functions: readonly FunctionData[]) {
|
function ensureValidFunctions(functions: readonly FunctionData[]) {
|
||||||
ensureNoUndefinedItem(functions);
|
ensureNoUndefinedItem(functions);
|
||||||
ensureNoDuplicatesInFunctionNames(functions);
|
ensureNoDuplicatesInFunctionNames(functions);
|
||||||
ensureNoDuplicatesInParameterNames(functions);
|
|
||||||
ensureNoDuplicateCode(functions);
|
ensureNoDuplicateCode(functions);
|
||||||
ensureEitherCallOrCodeIsDefined(functions);
|
ensureEitherCallOrCodeIsDefined(functions);
|
||||||
ensureExpectedParameterNameTypes(functions);
|
ensureExpectedParametersType(functions);
|
||||||
}
|
}
|
||||||
|
|
||||||
function printList(list: readonly string[]): string {
|
function printList(list: readonly string[]): string {
|
||||||
@@ -69,16 +90,20 @@ function ensureEitherCallOrCodeIsDefined(holders: readonly InstructionHolder[])
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureExpectedParameterNameTypes(functions: readonly FunctionData[]) {
|
function ensureExpectedParametersType(functions: readonly FunctionData[]) {
|
||||||
const unexpectedFunctions = functions.filter((func) => func.parameters && !isArrayOfStrings(func.parameters));
|
const unexpectedFunctions = functions
|
||||||
|
.filter((func) => func.parameters && !isArrayOfObjects(func.parameters));
|
||||||
if (unexpectedFunctions.length) {
|
if (unexpectedFunctions.length) {
|
||||||
throw new Error(`unexpected parameter name type in ${printNames(unexpectedFunctions)}`);
|
const errorMessage = `parameters must be an array of objects in function(s) ${printNames(unexpectedFunctions)}`;
|
||||||
}
|
throw new Error(errorMessage);
|
||||||
function isArrayOfStrings(value: any): boolean {
|
|
||||||
return Array.isArray(value) && value.every((item) => typeof item === 'string');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isArrayOfObjects(value: any): boolean {
|
||||||
|
return Array.isArray(value)
|
||||||
|
&& value.every((item) => typeof item === 'object');
|
||||||
|
}
|
||||||
|
|
||||||
function printNames(holders: readonly InstructionHolder[]) {
|
function printNames(holders: readonly InstructionHolder[]) {
|
||||||
return printList(holders.map((holder) => holder.name));
|
return printList(holders.map((holder) => holder.name));
|
||||||
}
|
}
|
||||||
@@ -90,21 +115,13 @@ function ensureNoDuplicatesInFunctionNames(functions: readonly FunctionData[]) {
|
|||||||
throw new Error(`duplicate function name: ${printList(duplicateFunctionNames)}`);
|
throw new Error(`duplicate function name: ${printList(duplicateFunctionNames)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureNoUndefinedItem(functions: readonly FunctionData[]) {
|
function ensureNoUndefinedItem(functions: readonly FunctionData[]) {
|
||||||
if (functions.some((func) => !func)) {
|
if (functions.some((func) => !func)) {
|
||||||
throw new Error(`some functions are undefined`);
|
throw new Error(`some functions are undefined`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function ensureNoDuplicatesInParameterNames(functions: readonly FunctionData[]) {
|
|
||||||
const functionsWithParameters = functions
|
|
||||||
.filter((func) => func.parameters && func.parameters.length > 0);
|
|
||||||
for (const func of functionsWithParameters) {
|
|
||||||
const duplicateParameterNames = getDuplicates(func.parameters);
|
|
||||||
if (duplicateParameterNames.length) {
|
|
||||||
throw new Error(`"${func.name}": duplicate parameter name: ${printList(duplicateParameterNames)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function ensureNoDuplicateCode(functions: readonly FunctionData[]) {
|
function ensureNoDuplicateCode(functions: readonly FunctionData[]) {
|
||||||
const duplicateCodes = getDuplicates(functions
|
const duplicateCodes = getDuplicates(functions
|
||||||
.map((func) => func.code)
|
.map((func) => func.code)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
|
||||||
|
|
||||||
export interface ISharedFunction {
|
export interface ISharedFunction {
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly parameters?: readonly string[];
|
readonly parameters: IReadOnlyFunctionParameterCollection;
|
||||||
readonly code: string;
|
readonly code: string;
|
||||||
readonly revertCode?: string;
|
readonly revertCode?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { IFunctionParameter } from './IFunctionParameter';
|
||||||
|
import { ensureValidParameterName } from '../../ParameterNameValidator';
|
||||||
|
|
||||||
|
export class FunctionParameter implements IFunctionParameter {
|
||||||
|
constructor(
|
||||||
|
public readonly name: string,
|
||||||
|
public readonly isOptional: boolean) {
|
||||||
|
ensureValidParameterName(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { IFunctionParameterCollection } from './IFunctionParameterCollection';
|
||||||
|
import { IFunctionParameter } from './IFunctionParameter';
|
||||||
|
|
||||||
|
export class FunctionParameterCollection implements IFunctionParameterCollection {
|
||||||
|
private parameters = new Array<IFunctionParameter>();
|
||||||
|
|
||||||
|
public get all(): readonly IFunctionParameter[] {
|
||||||
|
return this.parameters;
|
||||||
|
}
|
||||||
|
public addParameter(parameter: IFunctionParameter) {
|
||||||
|
this.ensureValidParameter(parameter);
|
||||||
|
this.parameters.push(parameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private includesName(name: string) {
|
||||||
|
return this.parameters.find((existingParameter) => existingParameter.name === name);
|
||||||
|
}
|
||||||
|
private ensureValidParameter(parameter: IFunctionParameter) {
|
||||||
|
if (!parameter) {
|
||||||
|
throw new Error('undefined parameter');
|
||||||
|
}
|
||||||
|
if (this.includesName(parameter.name)) {
|
||||||
|
throw new Error(`duplicate parameter name: "${parameter.name}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface IFunctionParameter {
|
||||||
|
readonly name: string;
|
||||||
|
readonly isOptional: boolean;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { IFunctionParameter } from './IFunctionParameter';
|
||||||
|
|
||||||
|
export interface IReadOnlyFunctionParameterCollection {
|
||||||
|
readonly all: readonly IFunctionParameter[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFunctionParameterCollection extends IReadOnlyFunctionParameterCollection {
|
||||||
|
addParameter(parameter: IFunctionParameter): void;
|
||||||
|
}
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
import { ISharedFunction } from './ISharedFunction';
|
import { ISharedFunction } from './ISharedFunction';
|
||||||
|
import { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
|
||||||
|
|
||||||
export class SharedFunction implements ISharedFunction {
|
export class SharedFunction implements ISharedFunction {
|
||||||
public readonly parameters: readonly string[];
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly name: string,
|
public readonly name: string,
|
||||||
parameters: readonly string[],
|
public readonly parameters: IReadOnlyFunctionParameterCollection,
|
||||||
public readonly code: string,
|
public readonly code: string,
|
||||||
public readonly revertCode: string,
|
public readonly revertCode?: string,
|
||||||
) {
|
) {
|
||||||
if (!name) { throw new Error('undefined function name'); }
|
if (!name) { throw new Error('undefined function name'); }
|
||||||
if (!code) { throw new Error(`undefined function ("${name}") code`); }
|
if (!code) { throw new Error(`undefined function ("${name}") code`); }
|
||||||
this.parameters = parameters || [];
|
if (!parameters) { throw new Error(`undefined parameters`); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { IFunctionCallArgument } from './IFunctionCallArgument';
|
||||||
|
import { ensureValidParameterName } from '../../ParameterNameValidator';
|
||||||
|
|
||||||
|
export class FunctionCallArgument implements IFunctionCallArgument {
|
||||||
|
constructor(
|
||||||
|
public readonly parameterName: string,
|
||||||
|
public readonly argumentValue: string) {
|
||||||
|
ensureValidParameterName(parameterName);
|
||||||
|
if (!argumentValue) {
|
||||||
|
throw new Error(`undefined argument value for "${parameterName}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { IFunctionCallArgument } from './IFunctionCallArgument';
|
||||||
|
import { IFunctionCallArgumentCollection } from './IFunctionCallArgumentCollection';
|
||||||
|
|
||||||
|
export class FunctionCallArgumentCollection implements IFunctionCallArgumentCollection {
|
||||||
|
private readonly arguments = new Map<string, IFunctionCallArgument>();
|
||||||
|
public addArgument(argument: IFunctionCallArgument): void {
|
||||||
|
if (!argument) {
|
||||||
|
throw new Error('undefined argument');
|
||||||
|
}
|
||||||
|
if (this.hasArgument(argument.parameterName)) {
|
||||||
|
throw new Error(`argument value for parameter ${argument.parameterName} is already provided`);
|
||||||
|
}
|
||||||
|
this.arguments.set(argument.parameterName, argument);
|
||||||
|
}
|
||||||
|
public getAllParameterNames(): string[] {
|
||||||
|
return Array.from(this.arguments.keys());
|
||||||
|
}
|
||||||
|
public hasArgument(parameterName: string): boolean {
|
||||||
|
if (!parameterName) {
|
||||||
|
throw new Error('undefined parameter name');
|
||||||
|
}
|
||||||
|
return this.arguments.has(parameterName);
|
||||||
|
}
|
||||||
|
public getArgument(parameterName: string): IFunctionCallArgument {
|
||||||
|
if (!parameterName) {
|
||||||
|
throw new Error('undefined parameter name');
|
||||||
|
}
|
||||||
|
const arg = this.arguments.get(parameterName);
|
||||||
|
if (!arg) {
|
||||||
|
throw new Error(`parameter does not exist: ${parameterName}`);
|
||||||
|
}
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface IFunctionCallArgument {
|
||||||
|
readonly parameterName: string;
|
||||||
|
readonly argumentValue: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { IFunctionCallArgument } from './IFunctionCallArgument';
|
||||||
|
|
||||||
|
export interface IReadOnlyFunctionCallArgumentCollection {
|
||||||
|
getArgument(parameterName: string): IFunctionCallArgument;
|
||||||
|
getAllParameterNames(): string[];
|
||||||
|
hasArgument(parameterName: string): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFunctionCallArgumentCollection extends IReadOnlyFunctionCallArgumentCollection {
|
||||||
|
addArgument(argument: IFunctionCallArgument): void;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { IReadOnlyFunctionCallArgumentCollection } from './Argument/IFunctionCallArgumentCollection';
|
||||||
|
import { IFunctionCall } from './IFunctionCall';
|
||||||
|
|
||||||
|
export class FunctionCall implements IFunctionCall {
|
||||||
|
constructor(
|
||||||
|
public readonly functionName: string,
|
||||||
|
public readonly args: IReadOnlyFunctionCallArgumentCollection) {
|
||||||
|
if (!functionName) {
|
||||||
|
throw new Error('empty function name in function call');
|
||||||
|
}
|
||||||
|
if (!args) {
|
||||||
|
throw new Error('undefined args');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,63 +1,63 @@
|
|||||||
import { FunctionCallData, FunctionCallParametersData, FunctionData, ScriptFunctionCallData } from 'js-yaml-loader!@/*';
|
import { FunctionCallData, ScriptFunctionCallData } from 'js-yaml-loader!@/*';
|
||||||
import { ICompiledCode } from './ICompiledCode';
|
import { ICompiledCode } from './ICompiledCode';
|
||||||
import { ISharedFunctionCollection } from '../Function/ISharedFunctionCollection';
|
import { ISharedFunctionCollection } from '../Function/ISharedFunctionCollection';
|
||||||
import { IFunctionCallCompiler } from './IFunctionCallCompiler';
|
import { IFunctionCallCompiler } from './IFunctionCallCompiler';
|
||||||
import { IExpressionsCompiler } from '../Expressions/IExpressionsCompiler';
|
import { IExpressionsCompiler } from '../Expressions/IExpressionsCompiler';
|
||||||
import { ExpressionsCompiler } from '../Expressions/ExpressionsCompiler';
|
import { ExpressionsCompiler } from '../Expressions/ExpressionsCompiler';
|
||||||
|
import { ISharedFunction } from '../Function/ISharedFunction';
|
||||||
|
import { IFunctionCall } from './IFunctionCall';
|
||||||
|
import { FunctionCall } from './FunctionCall';
|
||||||
|
import { FunctionCallArgument } from './Argument/FunctionCallArgument';
|
||||||
|
import { IReadOnlyFunctionCallArgumentCollection } from './Argument/IFunctionCallArgumentCollection';
|
||||||
|
import { FunctionCallArgumentCollection } from './Argument/FunctionCallArgumentCollection';
|
||||||
|
|
||||||
export class FunctionCallCompiler implements IFunctionCallCompiler {
|
export class FunctionCallCompiler implements IFunctionCallCompiler {
|
||||||
public static readonly instance: IFunctionCallCompiler = new FunctionCallCompiler();
|
public static readonly instance: IFunctionCallCompiler = new FunctionCallCompiler();
|
||||||
|
|
||||||
protected constructor(
|
protected constructor(
|
||||||
private readonly expressionsCompiler: IExpressionsCompiler = new ExpressionsCompiler()) { }
|
private readonly expressionsCompiler: IExpressionsCompiler = new ExpressionsCompiler()) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public compileCall(
|
public compileCall(
|
||||||
call: ScriptFunctionCallData,
|
call: ScriptFunctionCallData,
|
||||||
functions: ISharedFunctionCollection): ICompiledCode {
|
functions: ISharedFunctionCollection): ICompiledCode {
|
||||||
if (!functions) { throw new Error('undefined functions'); }
|
if (!functions) { throw new Error('undefined functions'); }
|
||||||
if (!call) { throw new Error('undefined call'); }
|
if (!call) { throw new Error('undefined call'); }
|
||||||
const compiledCodes = new Array<ICompiledCode>();
|
const compiledFunctions = new Array<ICompiledFunction>();
|
||||||
const calls = getCallSequence(call);
|
const callSequence = getCallSequence(call);
|
||||||
calls.forEach((currentCall, currentCallIndex) => {
|
for (const currentCall of callSequence) {
|
||||||
ensureValidCall(currentCall);
|
const functionCall = parseFunctionCall(currentCall);
|
||||||
const commonFunction = functions.getFunctionByName(currentCall.function);
|
const sharedFunction = functions.getFunctionByName(functionCall.functionName);
|
||||||
ensureExpectedParameters(commonFunction, currentCall);
|
ensureThatCallArgumentsExistInParameterDefinition(sharedFunction, functionCall.args);
|
||||||
let functionCode = compileCode(commonFunction, currentCall.parameters, this.expressionsCompiler);
|
const compiledFunction = compileCode(sharedFunction, functionCall.args, this.expressionsCompiler);
|
||||||
if (currentCallIndex !== calls.length - 1) {
|
compiledFunctions.push(compiledFunction);
|
||||||
functionCode = appendLine(functionCode);
|
|
||||||
}
|
}
|
||||||
compiledCodes.push(functionCode);
|
|
||||||
});
|
|
||||||
const compiledCode = merge(compiledCodes);
|
|
||||||
return compiledCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureExpectedParameters(func: FunctionData, call: FunctionCallData) {
|
|
||||||
const actual = Object.keys(call.parameters || {});
|
|
||||||
const expected = func.parameters || [];
|
|
||||||
if (!actual.length && !expected.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const unexpectedParameters = actual.filter((callParam) => !expected.includes(callParam));
|
|
||||||
if (unexpectedParameters.length) {
|
|
||||||
throw new Error(
|
|
||||||
`function "${func.name}" has unexpected parameter(s) provided: "${unexpectedParameters.join('", "')}"`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function merge(codes: readonly ICompiledCode[]): ICompiledCode {
|
|
||||||
return {
|
return {
|
||||||
code: codes.map((code) => code.code).join(''),
|
code: merge(compiledFunctions.map((f) => f.code)),
|
||||||
revertCode: codes.map((code) => code.revertCode).join(''),
|
revertCode: merge(compiledFunctions.map((f) => f.revertCode)),
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function merge(codeParts: readonly string[]): string {
|
||||||
|
return codeParts
|
||||||
|
.filter((part) => part?.length > 0)
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ICompiledFunction {
|
||||||
|
readonly code: string;
|
||||||
|
readonly revertCode: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function compileCode(
|
function compileCode(
|
||||||
func: FunctionData,
|
func: ISharedFunction,
|
||||||
parameters: FunctionCallParametersData,
|
args: IReadOnlyFunctionCallArgumentCollection,
|
||||||
compiler: IExpressionsCompiler): ICompiledCode {
|
compiler: IExpressionsCompiler): ICompiledFunction {
|
||||||
return {
|
return {
|
||||||
code: compiler.compileExpressions(func.code, parameters),
|
code: compiler.compileExpressions(func.code, args),
|
||||||
revertCode: compiler.compileExpressions(func.revertCode, parameters),
|
revertCode: compiler.compileExpressions(func.revertCode, args),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,19 +71,31 @@ function getCallSequence(call: ScriptFunctionCallData): FunctionCallData[] {
|
|||||||
return [ call as FunctionCallData ];
|
return [ call as FunctionCallData ];
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureValidCall(call: FunctionCallData) {
|
function parseFunctionCall(call: FunctionCallData): IFunctionCall {
|
||||||
if (!call) {
|
if (!call) {
|
||||||
throw new Error(`undefined function call`);
|
throw new Error(`undefined function call`);
|
||||||
}
|
}
|
||||||
if (!call.function) {
|
const args = new FunctionCallArgumentCollection();
|
||||||
throw new Error(`empty function name called`);
|
for (const parameterName of Object.keys(call.parameters || {})) {
|
||||||
|
const arg = new FunctionCallArgument(parameterName, call.parameters[parameterName]);
|
||||||
|
args.addArgument(arg);
|
||||||
}
|
}
|
||||||
|
return new FunctionCall(call.function, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendLine(code: ICompiledCode): ICompiledCode {
|
function ensureThatCallArgumentsExistInParameterDefinition(
|
||||||
const appendLineIfNotEmpty = (str: string) => str ? `${str}\n` : str;
|
func: ISharedFunction,
|
||||||
return {
|
args: IReadOnlyFunctionCallArgumentCollection): void {
|
||||||
code: appendLineIfNotEmpty(code.code),
|
const callArgumentNames = args.getAllParameterNames();
|
||||||
revertCode: appendLineIfNotEmpty(code.revertCode),
|
const functionParameterNames = func.parameters.all.map((param) => param.name) || [];
|
||||||
};
|
if (!callArgumentNames.length && !functionParameterNames.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const parametersOutsideFunction = callArgumentNames
|
||||||
|
.filter((callParam) => !functionParameterNames.includes(callParam));
|
||||||
|
if (parametersOutsideFunction.length) {
|
||||||
|
throw new Error(
|
||||||
|
`function "${func.name}" has unexpected parameter(s) provided:` +
|
||||||
|
`"${parametersOutsideFunction.join('", "')}"`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { IReadOnlyFunctionCallArgumentCollection } from './Argument/IFunctionCallArgumentCollection';
|
||||||
|
|
||||||
|
export interface IFunctionCall {
|
||||||
|
readonly functionName: string;
|
||||||
|
readonly args: IReadOnlyFunctionCallArgumentCollection;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export function ensureValidParameterName(parameterName: string) {
|
||||||
|
if (!parameterName) {
|
||||||
|
throw new Error('undefined parameter name');
|
||||||
|
}
|
||||||
|
if (!parameterName.match(/^[0-9a-zA-Z]+$/)) {
|
||||||
|
throw new Error(`parameter name must be alphanumeric but it was "${parameterName}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import { IExpressionsCompiler, ParameterValueDictionary } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
|
import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
|
||||||
import { ParameterSubstitutionParser } from '@/application/Parser/Script/Compiler/Expressions/SyntaxParsers/ParameterSubstitutionParser';
|
import { ParameterSubstitutionParser } from '@/application/Parser/Script/Compiler/Expressions/SyntaxParsers/ParameterSubstitutionParser';
|
||||||
import { CompositeExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser';
|
import { CompositeExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser';
|
||||||
import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler';
|
import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler';
|
||||||
import { IProjectInformation } from '@/domain/IProjectInformation';
|
import { IProjectInformation } from '@/domain/IProjectInformation';
|
||||||
import { ICodeSubstituter } from './ICodeSubstituter';
|
import { ICodeSubstituter } from './ICodeSubstituter';
|
||||||
|
import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/FunctionCallArgumentCollection';
|
||||||
|
import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/FunctionCallArgument';
|
||||||
|
|
||||||
export class CodeSubstituter implements ICodeSubstituter {
|
export class CodeSubstituter implements ICodeSubstituter {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -15,12 +17,13 @@ export class CodeSubstituter implements ICodeSubstituter {
|
|||||||
public substitute(code: string, info: IProjectInformation): string {
|
public substitute(code: string, info: IProjectInformation): string {
|
||||||
if (!code) { throw new Error('undefined code'); }
|
if (!code) { throw new Error('undefined code'); }
|
||||||
if (!info) { throw new Error('undefined info'); }
|
if (!info) { throw new Error('undefined info'); }
|
||||||
const parameters: ParameterValueDictionary = {
|
const args = new FunctionCallArgumentCollection();
|
||||||
homepage: info.homepage,
|
const substitute = (name: string, value: string) =>
|
||||||
version: info.version,
|
args.addArgument(new FunctionCallArgument(name, value));
|
||||||
date: this.date.toUTCString(),
|
substitute('homepage', info.homepage);
|
||||||
};
|
substitute('version', info.version);
|
||||||
const compiledCode = this.compiler.compileExpressions(code, parameters);
|
substitute('date', this.date.toUTCString());
|
||||||
|
const compiledCode = this.compiler.compileExpressions(code, args);
|
||||||
return compiledCode;
|
return compiledCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,13 @@ declare module 'js-yaml-loader!@/*' {
|
|||||||
readonly call?: ScriptFunctionCallData;
|
readonly call?: ScriptFunctionCallData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ParameterDefinitionData {
|
||||||
|
readonly name: string;
|
||||||
|
readonly optional?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface FunctionData extends InstructionHolder {
|
export interface FunctionData extends InstructionHolder {
|
||||||
readonly parameters?: readonly string[];
|
readonly parameters?: readonly ParameterDefinitionData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FunctionCallParametersData {
|
export interface FunctionCallParametersData {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Structure documented in "docs/collections.md"
|
# Structure documented in "docs/collection-files.md"
|
||||||
os: macos
|
os: macos
|
||||||
scripting:
|
scripting:
|
||||||
language: shellscript
|
language: shellscript
|
||||||
@@ -532,7 +532,8 @@ actions:
|
|||||||
functions:
|
functions:
|
||||||
-
|
-
|
||||||
name: PersistUserEnvironmentConfiguration
|
name: PersistUserEnvironmentConfiguration
|
||||||
parameters: [ configuration ]
|
parameters:
|
||||||
|
- name: configuration
|
||||||
code: |-
|
code: |-
|
||||||
command='{{ $configuration }}'
|
command='{{ $configuration }}'
|
||||||
declare -a profile_files=("$HOME/.bash_profile" "$HOME/.zprofile")
|
declare -a profile_files=("$HOME/.bash_profile" "$HOME/.zprofile")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Structure documented in "docs/collections.md"
|
# Structure documented in "docs/collection-files.md"
|
||||||
os: windows
|
os: windows
|
||||||
scripting:
|
scripting:
|
||||||
language: batchfile
|
language: batchfile
|
||||||
@@ -4387,18 +4387,21 @@ actions:
|
|||||||
functions:
|
functions:
|
||||||
-
|
-
|
||||||
name: KillProcessWhenItStarts
|
name: KillProcessWhenItStarts
|
||||||
parameters: [ processName ]
|
parameters:
|
||||||
|
- name: processName
|
||||||
# https://docs.microsoft.com/en-us/previous-versions/windows/desktop/xperf/image-file-execution-options
|
# https://docs.microsoft.com/en-us/previous-versions/windows/desktop/xperf/image-file-execution-options
|
||||||
code: reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\'{{ $processName }}'" /v "Debugger" /t REG_SZ /d "%windir%\System32\taskkill.exe" /f
|
code: reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\'{{ $processName }}'" /v "Debugger" /t REG_SZ /d "%windir%\System32\taskkill.exe" /f
|
||||||
revertCode: reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\'{{ $processName }}'" /v "Debugger" /f
|
revertCode: reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\'{{ $processName }}'" /v "Debugger" /f
|
||||||
-
|
-
|
||||||
name: DisableFeature
|
name: DisableFeature
|
||||||
parameters: [ featureName ]
|
parameters:
|
||||||
|
- name: featureName
|
||||||
code: dism /Online /Disable-Feature /FeatureName:"{{ $featureName }}" /NoRestart
|
code: dism /Online /Disable-Feature /FeatureName:"{{ $featureName }}" /NoRestart
|
||||||
revertCode: dism /Online /Enable-Feature /FeatureName:"{{ $featureName }}" /NoRestart
|
revertCode: dism /Online /Enable-Feature /FeatureName:"{{ $featureName }}" /NoRestart
|
||||||
-
|
-
|
||||||
name: UninstallStoreApp
|
name: UninstallStoreApp
|
||||||
parameters: [ packageName ]
|
parameters:
|
||||||
|
- name: packageName
|
||||||
call:
|
call:
|
||||||
function: RunPowerShell
|
function: RunPowerShell
|
||||||
parameters:
|
parameters:
|
||||||
@@ -4412,7 +4415,8 @@ functions:
|
|||||||
Add-AppxPackage -DisableDevelopmentMode -Register \"$manifest\"
|
Add-AppxPackage -DisableDevelopmentMode -Register \"$manifest\"
|
||||||
-
|
-
|
||||||
name: UninstallSystemApp
|
name: UninstallSystemApp
|
||||||
parameters: [ packageName ]
|
parameters:
|
||||||
|
- name: packageName
|
||||||
# It simply renames files
|
# It simply renames files
|
||||||
# Because system apps are non removable (check: (Get-AppxPackage -AllUsers 'Windows.CBSPreview').NonRemovable)
|
# Because system apps are non removable (check: (Get-AppxPackage -AllUsers 'Windows.CBSPreview').NonRemovable)
|
||||||
# Otherwise they throw 0x80070032 when trying to uninstall them
|
# Otherwise they throw 0x80070032 when trying to uninstall them
|
||||||
@@ -4457,7 +4461,8 @@ functions:
|
|||||||
}
|
}
|
||||||
-
|
-
|
||||||
name: UninstallCapability
|
name: UninstallCapability
|
||||||
parameters: [ capabilityName ]
|
parameters:
|
||||||
|
- name: capabilityName
|
||||||
call:
|
call:
|
||||||
function: RunPowerShell
|
function: RunPowerShell
|
||||||
parameters:
|
parameters:
|
||||||
@@ -4467,7 +4472,8 @@ functions:
|
|||||||
Add-WindowsCapability -Name \"$capability.Name\" -Online
|
Add-WindowsCapability -Name \"$capability.Name\" -Online
|
||||||
-
|
-
|
||||||
name: RenameSystemFile
|
name: RenameSystemFile
|
||||||
parameters: [ filePath ]
|
parameters:
|
||||||
|
- name: filePath
|
||||||
code: |-
|
code: |-
|
||||||
if exist "{{ $filePath }}" (
|
if exist "{{ $filePath }}" (
|
||||||
takeown /f "{{ $filePath }}"
|
takeown /f "{{ $filePath }}"
|
||||||
@@ -4488,7 +4494,9 @@ functions:
|
|||||||
)
|
)
|
||||||
-
|
-
|
||||||
name: SetVsCodeSetting
|
name: SetVsCodeSetting
|
||||||
parameters: [ setting, powerShellValue ]
|
parameters:
|
||||||
|
- name: setting
|
||||||
|
- name: powerShellValue
|
||||||
call:
|
call:
|
||||||
function: RunPowerShell
|
function: RunPowerShell
|
||||||
parameters:
|
parameters:
|
||||||
@@ -4511,6 +4519,8 @@ functions:
|
|||||||
$json | ConvertTo-Json | Set-Content $jsonfile;
|
$json | ConvertTo-Json | Set-Content $jsonfile;
|
||||||
-
|
-
|
||||||
name: RunPowerShell
|
name: RunPowerShell
|
||||||
parameters: [ code, revertCode ]
|
parameters:
|
||||||
|
- name: code
|
||||||
|
- name: revertCode
|
||||||
code: PowerShell -ExecutionPolicy Unrestricted -Command "{{ $code }}"
|
code: PowerShell -ExecutionPolicy Unrestricted -Command "{{ $code }}"
|
||||||
revertCode: PowerShell -ExecutionPolicy Unrestricted -Command "{{ $revertCode }}"
|
revertCode: PowerShell -ExecutionPolicy Unrestricted -Command "{{ $revertCode }}"
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ import { expect } from 'chai';
|
|||||||
import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition';
|
import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition';
|
||||||
import { ExpressionEvaluator } from '@/application/Parser/Script/Compiler/Expressions/Expression/Expression';
|
import { ExpressionEvaluator } from '@/application/Parser/Script/Compiler/Expressions/Expression/Expression';
|
||||||
import { Expression } from '@/application/Parser/Script/Compiler/Expressions/Expression/Expression';
|
import { Expression } from '@/application/Parser/Script/Compiler/Expressions/Expression/Expression';
|
||||||
import { ExpressionArguments } from '@/application/Parser/Script/Compiler/Expressions/Expression/IExpression';
|
import { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
|
||||||
|
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/IFunctionCallArgumentCollection';
|
||||||
|
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
||||||
|
import { FunctionParameterCollectionStub } from '@tests/unit/stubs/FunctionParameterCollectionStub';
|
||||||
|
import { FunctionCallArgumentStub } from '@tests/unit/stubs/FunctionCallArgumentStub';
|
||||||
|
|
||||||
describe('Expression', () => {
|
describe('Expression', () => {
|
||||||
describe('ctor', () => {
|
describe('ctor', () => {
|
||||||
@@ -39,11 +43,15 @@ describe('Expression', () => {
|
|||||||
.withParameters(parameters)
|
.withParameters(parameters)
|
||||||
.build();
|
.build();
|
||||||
// assert
|
// assert
|
||||||
expect(actual.parameters).to.have.lengthOf(0);
|
expect(actual.parameters);
|
||||||
|
expect(actual.parameters.all);
|
||||||
|
expect(actual.parameters.all.length).to.equal(0);
|
||||||
});
|
});
|
||||||
it('sets as expected', () => {
|
it('sets as expected', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const expected = [ 'firstParameterName', 'secondParameterName' ];
|
const expected = new FunctionParameterCollectionStub()
|
||||||
|
.withParameterName('firstParameterName')
|
||||||
|
.withParameterName('secondParameterName');
|
||||||
// act
|
// act
|
||||||
const actual = new ExpressionBuilder()
|
const actual = new ExpressionBuilder()
|
||||||
.withParameters(expected)
|
.withParameters(expected)
|
||||||
@@ -67,52 +75,119 @@ describe('Expression', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('evaluate', () => {
|
describe('evaluate', () => {
|
||||||
|
describe('throws with invalid arguments', () => {
|
||||||
|
const testCases = [
|
||||||
|
{
|
||||||
|
name: 'throws if arguments is undefined',
|
||||||
|
args: undefined,
|
||||||
|
expectedError: 'undefined args, send empty collection instead',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'throws when some of the required args are not provided',
|
||||||
|
sut: (i: ExpressionBuilder) => i.withParameterNames(['a', 'b', 'c'], false),
|
||||||
|
args: new FunctionCallArgumentCollectionStub().withArgument('b', 'provided'),
|
||||||
|
expectedError: 'argument values are provided for required parameters: "a", "c"',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'throws when none of the required args are not provided',
|
||||||
|
sut: (i: ExpressionBuilder) => i.withParameterNames(['a', 'b'], false),
|
||||||
|
args: new FunctionCallArgumentCollectionStub().withArgument('c', 'unrelated'),
|
||||||
|
expectedError: 'argument values are provided for required parameters: "a", "b"',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
for (const testCase of testCases) {
|
||||||
|
it(testCase.name, () => {
|
||||||
|
// arrange
|
||||||
|
const sutBuilder = new ExpressionBuilder();
|
||||||
|
if (testCase.sut) {
|
||||||
|
testCase.sut(sutBuilder);
|
||||||
|
}
|
||||||
|
const sut = sutBuilder.build();
|
||||||
|
// act
|
||||||
|
const act = () => sut.evaluate(testCase.args);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(testCase.expectedError);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
it('returns result from evaluator', () => {
|
it('returns result from evaluator', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const evaluatorMock: ExpressionEvaluator = (args) => JSON.stringify(args);
|
const evaluatorMock: ExpressionEvaluator = (args) =>
|
||||||
const givenArguments = { parameter1: 'value1', parameter2: 'value2' };
|
`"${args
|
||||||
|
.getAllParameterNames()
|
||||||
|
.map((name) => args.getArgument(name))
|
||||||
|
.map((arg) => `${arg.parameterName}': '${arg.argumentValue}'`)
|
||||||
|
.join('", "')}"`;
|
||||||
|
const givenArguments = new FunctionCallArgumentCollectionStub()
|
||||||
|
.withArgument('parameter1', 'value1')
|
||||||
|
.withArgument('parameter2', 'value2');
|
||||||
|
const expectedParameterNames = givenArguments.getAllParameterNames();
|
||||||
const expected = evaluatorMock(givenArguments);
|
const expected = evaluatorMock(givenArguments);
|
||||||
const sut = new ExpressionBuilder()
|
const sut = new ExpressionBuilder()
|
||||||
.withEvaluator(evaluatorMock)
|
.withEvaluator(evaluatorMock)
|
||||||
.withParameters(Object.keys(givenArguments))
|
.withParameterNames(expectedParameterNames)
|
||||||
.build();
|
.build();
|
||||||
// arrange
|
// arrange
|
||||||
const actual = sut.evaluate(givenArguments);
|
const actual = sut.evaluate(givenArguments);
|
||||||
// assert
|
// assert
|
||||||
expect(expected).to.equal(actual);
|
expect(expected).to.equal(actual,
|
||||||
|
`\nGiven arguments: ${JSON.stringify(givenArguments)}\n` +
|
||||||
|
`\nExpected parameter names: ${JSON.stringify(expectedParameterNames)}\n`,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
it('filters unused arguments', () => {
|
describe('filters unused parameters', () => {
|
||||||
// arrange
|
// arrange
|
||||||
let actual: ExpressionArguments = {};
|
const testCases = [
|
||||||
|
{
|
||||||
|
name: 'with a provided argument',
|
||||||
|
expressionParameters: new FunctionParameterCollectionStub()
|
||||||
|
.withParameterName('parameterToHave', false),
|
||||||
|
arguments: new FunctionCallArgumentCollectionStub()
|
||||||
|
.withArgument('parameterToHave', 'value-to-have')
|
||||||
|
.withArgument('parameterToIgnore', 'value-to-ignore'),
|
||||||
|
expectedArguments: [
|
||||||
|
new FunctionCallArgumentStub()
|
||||||
|
.withParameterName('parameterToHave').withArgumentValue('value-to-have'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'without a provided argument',
|
||||||
|
expressionParameters: new FunctionParameterCollectionStub()
|
||||||
|
.withParameterName('parameterToHave', false)
|
||||||
|
.withParameterName('parameterToIgnore', true),
|
||||||
|
arguments: new FunctionCallArgumentCollectionStub()
|
||||||
|
.withArgument('parameterToHave', 'value-to-have'),
|
||||||
|
expectedArguments: [
|
||||||
|
new FunctionCallArgumentStub()
|
||||||
|
.withParameterName('parameterToHave').withArgumentValue('value-to-have'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
for (const testCase of testCases) {
|
||||||
|
it(testCase.name, () => {
|
||||||
|
let actual: IReadOnlyFunctionCallArgumentCollection;
|
||||||
const evaluatorMock: ExpressionEvaluator = (providedArgs) => {
|
const evaluatorMock: ExpressionEvaluator = (providedArgs) => {
|
||||||
Object.keys(providedArgs)
|
actual = providedArgs;
|
||||||
.forEach((name) => actual = {...actual, [name]: providedArgs[name] });
|
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
const parameterNameToHave = 'parameterToHave';
|
|
||||||
const parameterNameToIgnore = 'parameterToIgnore';
|
|
||||||
const sut = new ExpressionBuilder()
|
const sut = new ExpressionBuilder()
|
||||||
.withEvaluator(evaluatorMock)
|
.withEvaluator(evaluatorMock)
|
||||||
.withParameters([ parameterNameToHave ])
|
.withParameters(testCase.expressionParameters)
|
||||||
.build();
|
.build();
|
||||||
const args: ExpressionArguments = {
|
// act
|
||||||
[parameterNameToHave]: 'value-to-have',
|
sut.evaluate(testCase.arguments);
|
||||||
[parameterNameToIgnore]: 'value-to-ignore',
|
|
||||||
};
|
|
||||||
const expected: ExpressionArguments = {
|
|
||||||
[parameterNameToHave]: args[parameterNameToHave],
|
|
||||||
};
|
|
||||||
// arrange
|
|
||||||
sut.evaluate(args);
|
|
||||||
// assert
|
// assert
|
||||||
expect(expected).to.deep.equal(actual);
|
const actualArguments = actual.getAllParameterNames().map((name) => actual.getArgument(name));
|
||||||
|
expect(actualArguments).to.deep.equal(testCase.expectedArguments);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
class ExpressionBuilder {
|
class ExpressionBuilder {
|
||||||
private position: ExpressionPosition = new ExpressionPosition(0, 5);
|
private position: ExpressionPosition = new ExpressionPosition(0, 5);
|
||||||
private parameters: readonly string[] = new Array<string>();
|
private parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub();
|
||||||
|
|
||||||
public withPosition(position: ExpressionPosition) {
|
public withPosition(position: ExpressionPosition) {
|
||||||
this.position = position;
|
this.position = position;
|
||||||
@@ -122,10 +197,20 @@ class ExpressionBuilder {
|
|||||||
this.evaluator = evaluator;
|
this.evaluator = evaluator;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public withParameters(parameters: string[]) {
|
public withParameters(parameters: IReadOnlyFunctionParameterCollection) {
|
||||||
this.parameters = parameters;
|
this.parameters = parameters;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
public withParameterName(parameterName: string, isOptional: boolean = true) {
|
||||||
|
const collection = new FunctionParameterCollectionStub()
|
||||||
|
.withParameterName(parameterName, isOptional);
|
||||||
|
return this.withParameters(collection);
|
||||||
|
}
|
||||||
|
public withParameterNames(parameterNames: string[], isOptional: boolean = true) {
|
||||||
|
const collection = new FunctionParameterCollectionStub()
|
||||||
|
.withParameterNames(parameterNames, isOptional);
|
||||||
|
return this.withParameters(collection);
|
||||||
|
}
|
||||||
public build() {
|
public build() {
|
||||||
return new Expression(this.position, this.evaluator, this.parameters);
|
return new Expression(this.position, this.evaluator, this.parameters);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expres
|
|||||||
import { IExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/IExpressionParser';
|
import { IExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/IExpressionParser';
|
||||||
import { ExpressionStub } from '@tests/unit/stubs/ExpressionStub';
|
import { ExpressionStub } from '@tests/unit/stubs/ExpressionStub';
|
||||||
import { ExpressionParserStub } from '@tests/unit/stubs/ExpressionParserStub';
|
import { ExpressionParserStub } from '@tests/unit/stubs/ExpressionParserStub';
|
||||||
|
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
||||||
|
|
||||||
describe('ExpressionsCompiler', () => {
|
describe('ExpressionsCompiler', () => {
|
||||||
describe('compileExpressions', () => {
|
describe('compileExpressions', () => {
|
||||||
@@ -22,8 +23,18 @@ describe('ExpressionsCompiler', () => {
|
|||||||
{
|
{
|
||||||
name: 'unordered expressions',
|
name: 'unordered expressions',
|
||||||
expressions: [
|
expressions: [
|
||||||
new ExpressionStub().withPosition(6, 13).withEvaluatedResult('a'),
|
|
||||||
new ExpressionStub().withPosition(20, 27).withEvaluatedResult('b'),
|
new ExpressionStub().withPosition(20, 27).withEvaluatedResult('b'),
|
||||||
|
new ExpressionStub().withPosition(6, 13).withEvaluatedResult('a'),
|
||||||
|
],
|
||||||
|
expected: 'part1 a part2 b part3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'with an optional expected argument that is not provided',
|
||||||
|
expressions: [
|
||||||
|
new ExpressionStub().withPosition(6, 13).withEvaluatedResult('a')
|
||||||
|
.withParameterNames(['optionalParameter'], true),
|
||||||
|
new ExpressionStub().withPosition(20, 27).withEvaluatedResult('b')
|
||||||
|
.withParameterNames(['optionalParameterTwo'], true),
|
||||||
],
|
],
|
||||||
expected: 'part1 a part2 b part3',
|
expected: 'part1 a part2 b part3',
|
||||||
},
|
},
|
||||||
@@ -37,20 +48,20 @@ describe('ExpressionsCompiler', () => {
|
|||||||
it(testCase.name, () => {
|
it(testCase.name, () => {
|
||||||
const expressionParserMock = new ExpressionParserStub()
|
const expressionParserMock = new ExpressionParserStub()
|
||||||
.withResult(testCase.expressions);
|
.withResult(testCase.expressions);
|
||||||
|
const args = new FunctionCallArgumentCollectionStub();
|
||||||
const sut = new MockableExpressionsCompiler(expressionParserMock);
|
const sut = new MockableExpressionsCompiler(expressionParserMock);
|
||||||
// act
|
// act
|
||||||
const actual = sut.compileExpressions(code);
|
const actual = sut.compileExpressions(code, args);
|
||||||
// assert
|
// assert
|
||||||
expect(actual).to.equal(testCase.expected);
|
expect(actual).to.equal(testCase.expected);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
describe('arguments', () => {
|
||||||
it('passes arguments to expressions as expected', () => {
|
it('passes arguments to expressions as expected', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const expected = {
|
const expected = new FunctionCallArgumentCollectionStub()
|
||||||
parameter1: 'value1',
|
.withArgument('test-arg', 'test-value');
|
||||||
parameter2: 'value2',
|
|
||||||
};
|
|
||||||
const code = 'non-important';
|
const code = 'non-important';
|
||||||
const expressions = [
|
const expressions = [
|
||||||
new ExpressionStub(),
|
new ExpressionStub(),
|
||||||
@@ -67,67 +78,59 @@ describe('ExpressionsCompiler', () => {
|
|||||||
expect(expressions[1].callHistory).to.have.lengthOf(1);
|
expect(expressions[1].callHistory).to.have.lengthOf(1);
|
||||||
expect(expressions[1].callHistory[0]).to.equal(expected);
|
expect(expressions[1].callHistory[0]).to.equal(expected);
|
||||||
});
|
});
|
||||||
describe('throws when expected argument is not provided', () => {
|
it('throws if arguments is undefined', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const noParameterTestCases = [
|
const expectedError = 'undefined args, send empty collection instead';
|
||||||
|
const args = undefined;
|
||||||
|
const expressionParserMock = new ExpressionParserStub();
|
||||||
|
const sut = new MockableExpressionsCompiler(expressionParserMock);
|
||||||
|
// act
|
||||||
|
const act = () => sut.compileExpressions('code', args);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('throws when expected argument is not provided but used in code', () => {
|
||||||
|
// arrange
|
||||||
|
const testCases = [
|
||||||
{
|
{
|
||||||
name: 'empty parameters',
|
name: 'empty parameters',
|
||||||
expressions: [
|
expressions: [
|
||||||
new ExpressionStub().withParameters('parameter'),
|
new ExpressionStub().withParameterNames(['parameter'], false),
|
||||||
],
|
],
|
||||||
args: {},
|
args: new FunctionCallArgumentCollectionStub(),
|
||||||
expectedError: 'parameter value(s) not provided for: "parameter"',
|
expectedError: 'parameter value(s) not provided for: "parameter" but used in code',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'undefined parameters',
|
name: 'unnecessary parameter is provided',
|
||||||
expressions: [
|
expressions: [
|
||||||
new ExpressionStub().withParameters('parameter'),
|
new ExpressionStub().withParameterNames(['parameter'], false),
|
||||||
],
|
],
|
||||||
args: undefined,
|
args: new FunctionCallArgumentCollectionStub()
|
||||||
expectedError: 'parameter value(s) not provided for: "parameter"',
|
.withArgument('unnecessaryParameter', 'unnecessaryValue'),
|
||||||
},
|
expectedError: 'parameter value(s) not provided for: "parameter" but used in code',
|
||||||
{
|
|
||||||
name: 'unnecessary parameter provided',
|
|
||||||
expressions: [
|
|
||||||
new ExpressionStub().withParameters('parameter'),
|
|
||||||
],
|
|
||||||
args: {
|
|
||||||
unnecessaryParameter: 'unnecessaryValue',
|
|
||||||
},
|
|
||||||
expectedError: 'parameter value(s) not provided for: "parameter"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'undefined value',
|
|
||||||
expressions: [
|
|
||||||
new ExpressionStub().withParameters('parameter'),
|
|
||||||
],
|
|
||||||
args: {
|
|
||||||
parameter: undefined,
|
|
||||||
},
|
|
||||||
expectedError: 'parameter value(s) not provided for: "parameter"',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'multiple values are not provided',
|
name: 'multiple values are not provided',
|
||||||
expressions: [
|
expressions: [
|
||||||
new ExpressionStub().withParameters('parameter1'),
|
new ExpressionStub().withParameterNames(['parameter1'], false),
|
||||||
new ExpressionStub().withParameters('parameter2', 'parameter3'),
|
new ExpressionStub().withParameterNames(['parameter2', 'parameter3'], false),
|
||||||
],
|
],
|
||||||
args: {},
|
args: new FunctionCallArgumentCollectionStub(),
|
||||||
expectedError: 'parameter value(s) not provided for: "parameter1", "parameter2", "parameter3"',
|
expectedError: 'parameter value(s) not provided for: "parameter1", "parameter2", "parameter3" but used in code',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'some values are provided',
|
name: 'some values are provided',
|
||||||
expressions: [
|
expressions: [
|
||||||
new ExpressionStub().withParameters('parameter1'),
|
new ExpressionStub().withParameterNames(['parameter1'], false),
|
||||||
new ExpressionStub().withParameters('parameter2', 'parameter3'),
|
new ExpressionStub().withParameterNames(['parameter2', 'parameter3'], false),
|
||||||
],
|
],
|
||||||
args: {
|
args: new FunctionCallArgumentCollectionStub()
|
||||||
parameter2: 'value',
|
.withArgument('parameter2', 'value'),
|
||||||
},
|
expectedError: 'parameter value(s) not provided for: "parameter1", "parameter3" but used in code',
|
||||||
expectedError: 'parameter value(s) not provided for: "parameter1", "parameter3"',
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
for (const testCase of noParameterTestCases) {
|
for (const testCase of testCases) {
|
||||||
it(testCase.name, () => {
|
it(testCase.name, () => {
|
||||||
const code = 'non-important-code';
|
const code = 'non-important-code';
|
||||||
const expressionParserMock = new ExpressionParserStub()
|
const expressionParserMock = new ExpressionParserStub()
|
||||||
@@ -145,8 +148,9 @@ describe('ExpressionsCompiler', () => {
|
|||||||
const expected = 'expected-code';
|
const expected = 'expected-code';
|
||||||
const expressionParserMock = new ExpressionParserStub();
|
const expressionParserMock = new ExpressionParserStub();
|
||||||
const sut = new MockableExpressionsCompiler(expressionParserMock);
|
const sut = new MockableExpressionsCompiler(expressionParserMock);
|
||||||
|
const args = new FunctionCallArgumentCollectionStub();
|
||||||
// act
|
// act
|
||||||
sut.compileExpressions(expected);
|
sut.compileExpressions(expected, args);
|
||||||
// assert
|
// assert
|
||||||
expect(expressionParserMock.callHistory).to.have.lengthOf(1);
|
expect(expressionParserMock.callHistory).to.have.lengthOf(1);
|
||||||
expect(expressionParserMock.callHistory[0]).to.equal(expected);
|
expect(expressionParserMock.callHistory[0]).to.equal(expected);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { expect } from 'chai';
|
|||||||
import { ExpressionEvaluator } from '@/application/Parser/Script/Compiler/Expressions/Expression/Expression';
|
import { ExpressionEvaluator } from '@/application/Parser/Script/Compiler/Expressions/Expression/Expression';
|
||||||
import { IPrimitiveExpression, RegexParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/RegexParser';
|
import { IPrimitiveExpression, RegexParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/RegexParser';
|
||||||
import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition';
|
import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition';
|
||||||
|
import { FunctionParameterStub } from '@tests/unit/stubs/FunctionParameterStub';
|
||||||
|
|
||||||
describe('RegexParser', () => {
|
describe('RegexParser', () => {
|
||||||
describe('findExpressions', () => {
|
describe('findExpressions', () => {
|
||||||
@@ -59,7 +60,10 @@ describe('RegexParser', () => {
|
|||||||
});
|
});
|
||||||
it('sets parameters as expected', () => {
|
it('sets parameters as expected', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const expected = [ 'parameter1', 'parameter2' ];
|
const expected = [
|
||||||
|
new FunctionParameterStub().withName('parameter1').withOptionality(true),
|
||||||
|
new FunctionParameterStub().withName('parameter2').withOptionality(false),
|
||||||
|
];
|
||||||
const regex = /hello/g;
|
const regex = /hello/g;
|
||||||
const code = 'hello';
|
const code = 'hello';
|
||||||
const builder = (): IPrimitiveExpression => ({
|
const builder = (): IPrimitiveExpression => ({
|
||||||
@@ -71,7 +75,7 @@ describe('RegexParser', () => {
|
|||||||
const expressions = sut.findExpressions(code);
|
const expressions = sut.findExpressions(code);
|
||||||
// assert
|
// assert
|
||||||
expect(expressions).to.have.lengthOf(1);
|
expect(expressions).to.have.lengthOf(1);
|
||||||
expect(expressions[0].parameters).to.equal(expected);
|
expect(expressions[0].parameters.all).to.deep.equal(expected);
|
||||||
});
|
});
|
||||||
it('sets expected position', () => {
|
it('sets expected position', () => {
|
||||||
// arrange
|
// arrange
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'mocha';
|
|||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { ParameterSubstitutionParser } from '@/application/Parser/Script/Compiler/Expressions/SyntaxParsers/ParameterSubstitutionParser';
|
import { ParameterSubstitutionParser } from '@/application/Parser/Script/Compiler/Expressions/SyntaxParsers/ParameterSubstitutionParser';
|
||||||
import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition';
|
import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition';
|
||||||
import { ExpressionArguments } from '@/application/Parser/Script/Compiler/Expressions/Expression/IExpression';
|
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
||||||
|
|
||||||
describe('ParameterSubstitutionParser', () => {
|
describe('ParameterSubstitutionParser', () => {
|
||||||
describe('finds at expected positions', () => {
|
describe('finds at expected positions', () => {
|
||||||
@@ -44,36 +44,25 @@ describe('ParameterSubstitutionParser', () => {
|
|||||||
const testCases = [ {
|
const testCases = [ {
|
||||||
name: 'single parameter',
|
name: 'single parameter',
|
||||||
code: '{{ $parameter }}',
|
code: '{{ $parameter }}',
|
||||||
args: [ {
|
args: new FunctionCallArgumentCollectionStub()
|
||||||
name: 'parameter',
|
.withArgument('parameter', 'Hello world'),
|
||||||
value: 'Hello world',
|
|
||||||
}],
|
|
||||||
expected: [ 'Hello world' ],
|
expected: [ 'Hello world' ],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'different parameters',
|
name: 'different parameters',
|
||||||
code: '{{ $firstParameter }} {{ $secondParameter }}!',
|
code: '{{ $firstParameter }} {{ $secondParameter }}!',
|
||||||
args: [ {
|
args: new FunctionCallArgumentCollectionStub()
|
||||||
name: 'firstParameter',
|
.withArgument('firstParameter', 'Hello')
|
||||||
value: 'Hello',
|
.withArgument('secondParameter', 'World'),
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'secondParameter',
|
|
||||||
value: 'World',
|
|
||||||
}],
|
|
||||||
expected: [ 'Hello', 'World' ],
|
expected: [ 'Hello', 'World' ],
|
||||||
}];
|
}];
|
||||||
for (const testCase of testCases) {
|
for (const testCase of testCases) {
|
||||||
it(testCase.name, () => {
|
it(testCase.name, () => {
|
||||||
const sut = new ParameterSubstitutionParser();
|
const sut = new ParameterSubstitutionParser();
|
||||||
let args: ExpressionArguments = {};
|
|
||||||
for (const arg of testCase.args) {
|
|
||||||
args = {...args, [arg.name]: arg.value };
|
|
||||||
}
|
|
||||||
// act
|
// act
|
||||||
const expressions = sut.findExpressions(testCase.code);
|
const expressions = sut.findExpressions(testCase.code);
|
||||||
// assert
|
// assert
|
||||||
const actual = expressions.map((e) => e.evaluate(args));
|
const actual = expressions.map((e) => e.evaluate(testCase.args));
|
||||||
expect(actual).to.deep.equal(testCase.expected);
|
expect(actual).to.deep.equal(testCase.expected);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import 'mocha';
|
import 'mocha';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||||
import { FunctionData } from 'js-yaml-loader!@/*';
|
import { FunctionData, ParameterDefinitionData } from 'js-yaml-loader!@/*';
|
||||||
import { IFunctionCallCompiler } from '@/application/Parser/Script/Compiler/FunctionCall/IFunctionCallCompiler';
|
import { IFunctionCallCompiler } from '@/application/Parser/Script/Compiler/FunctionCall/IFunctionCallCompiler';
|
||||||
import { FunctionCompiler } from '@/application/Parser/Script/Compiler/Function/FunctionCompiler';
|
import { FunctionCompiler } from '@/application/Parser/Script/Compiler/Function/FunctionCompiler';
|
||||||
|
import { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
|
||||||
import { FunctionCallCompilerStub } from '@tests/unit/stubs/FunctionCallCompilerStub';
|
import { FunctionCallCompilerStub } from '@tests/unit/stubs/FunctionCallCompilerStub';
|
||||||
import { FunctionDataStub } from '@tests/unit/stubs/FunctionDataStub';
|
import { FunctionDataStub } from '@tests/unit/stubs/FunctionDataStub';
|
||||||
|
import { ParameterDefinitionDataStub } from '@tests/unit/stubs/ParameterDefinitionDataStub';
|
||||||
|
import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameter';
|
||||||
|
|
||||||
describe('FunctionsCompiler', () => {
|
describe('FunctionsCompiler', () => {
|
||||||
describe('compileFunctions', () => {
|
describe('compileFunctions', () => {
|
||||||
@@ -34,29 +37,31 @@ describe('FunctionsCompiler', () => {
|
|||||||
// assert
|
// assert
|
||||||
expect(act).to.throw(expectedError);
|
expect(act).to.throw(expectedError);
|
||||||
});
|
});
|
||||||
it('throws when function parameters have same names', () => {
|
describe('throws when parameters type is not as expected', () => {
|
||||||
|
const testCases = [
|
||||||
|
{
|
||||||
|
state: 'when not an array',
|
||||||
|
invalidType: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
state: 'when array but not of objects',
|
||||||
|
invalidType: [ 'a', { a: 'b'} ],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
for (const testCase of testCases) {
|
||||||
|
it(testCase.state, () => {
|
||||||
// arrange
|
// arrange
|
||||||
const parameterName = 'duplicate-parameter';
|
const func = FunctionDataStub
|
||||||
const func = FunctionDataStub.createWithCall()
|
.createWithCall()
|
||||||
.withParameters(parameterName, parameterName);
|
.withParametersObject(testCase.invalidType as any);
|
||||||
const expectedError = `"${func.name}": duplicate parameter name: "${parameterName}"`;
|
const expectedError = `parameters must be an array of objects in function(s) "${func.name}"`;
|
||||||
const sut = new MockableFunctionCompiler();
|
const sut = new MockableFunctionCompiler();
|
||||||
// act
|
// act
|
||||||
const act = () => sut.compileFunctions([ func ]);
|
const act = () => sut.compileFunctions([ func ]);
|
||||||
// assert
|
// assert
|
||||||
expect(act).to.throw(expectedError);
|
expect(act).to.throw(expectedError);
|
||||||
});
|
});
|
||||||
it('throws when parameters is not an array of strings', () => {
|
}
|
||||||
// arrange
|
|
||||||
const parameterNameWithUnexpectedType = 5;
|
|
||||||
const func = FunctionDataStub.createWithCall()
|
|
||||||
.withParameters(parameterNameWithUnexpectedType as any);
|
|
||||||
const expectedError = `unexpected parameter name type in "${func.name}"`;
|
|
||||||
const sut = new MockableFunctionCompiler();
|
|
||||||
// act
|
|
||||||
const act = () => sut.compileFunctions([ func ]);
|
|
||||||
// assert
|
|
||||||
expect(act).to.throw(expectedError);
|
|
||||||
});
|
});
|
||||||
describe('throws when when function have duplicate code', () => {
|
describe('throws when when function have duplicate code', () => {
|
||||||
it('code', () => {
|
it('code', () => {
|
||||||
@@ -116,6 +121,37 @@ describe('FunctionsCompiler', () => {
|
|||||||
// assert
|
// assert
|
||||||
expect(act).to.throw(expectedError);
|
expect(act).to.throw(expectedError);
|
||||||
});
|
});
|
||||||
|
it('rethrows including function name when FunctionParameter throws', () => {
|
||||||
|
// arrange
|
||||||
|
const functionName = 'invalid-function';
|
||||||
|
const expectedError = `neither "code" or "call" is defined in "${functionName}"`;
|
||||||
|
const invalidFunction = FunctionDataStub.createWithoutCallOrCodes()
|
||||||
|
.withName(functionName);
|
||||||
|
const sut = new MockableFunctionCompiler();
|
||||||
|
// act
|
||||||
|
const act = () => sut.compileFunctions([ invalidFunction ]);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
it('rethrows including function name when FunctionParameter throws', () => {
|
||||||
|
// arrange
|
||||||
|
const invalidParameterName = 'invalid function p@r4meter name';
|
||||||
|
const functionName = 'functionName';
|
||||||
|
let parameterException: Error;
|
||||||
|
// tslint:disable-next-line:no-unused-expression
|
||||||
|
try { new FunctionParameter(invalidParameterName, false); } catch (e) { parameterException = e; }
|
||||||
|
const expectedError = `"${functionName}": ${parameterException.message}`;
|
||||||
|
const functionData = FunctionDataStub.createWithCode()
|
||||||
|
.withName(functionName)
|
||||||
|
.withParameters(new ParameterDefinitionDataStub().withName(invalidParameterName));
|
||||||
|
|
||||||
|
// act
|
||||||
|
const sut = new MockableFunctionCompiler();
|
||||||
|
const act = () => sut.compileFunctions([ functionData ]);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
it('returns empty with empty functions', () => {
|
it('returns empty with empty functions', () => {
|
||||||
// arrange
|
// arrange
|
||||||
@@ -136,7 +172,10 @@ describe('FunctionsCompiler', () => {
|
|||||||
.withName(name)
|
.withName(name)
|
||||||
.withCode('expected-code')
|
.withCode('expected-code')
|
||||||
.withRevertCode('expected-revert-code')
|
.withRevertCode('expected-revert-code')
|
||||||
.withParameters('expected-parameter-1', 'expected-parameter-2');
|
.withParameters(
|
||||||
|
new ParameterDefinitionDataStub().withName('expectedParameter').withOptionality(true),
|
||||||
|
new ParameterDefinitionDataStub().withName('expectedParameter2').withOptionality(false),
|
||||||
|
);
|
||||||
const sut = new MockableFunctionCompiler();
|
const sut = new MockableFunctionCompiler();
|
||||||
// act
|
// act
|
||||||
const collection = sut.compileFunctions([ expected ]);
|
const collection = sut.compileFunctions([ expected ]);
|
||||||
@@ -188,7 +227,7 @@ describe('FunctionsCompiler', () => {
|
|||||||
|
|
||||||
function expectEqualFunctions(expected: FunctionData, actual: ISharedFunction) {
|
function expectEqualFunctions(expected: FunctionData, actual: ISharedFunction) {
|
||||||
expect(actual.name).to.equal(expected.name);
|
expect(actual.name).to.equal(expected.name);
|
||||||
expect(actual.parameters).to.deep.equal(expected.parameters);
|
expect(areScrambledEqual(actual.parameters, expected.parameters));
|
||||||
expectEqualFunctionCode(expected, actual);
|
expectEqualFunctionCode(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,6 +236,23 @@ function expectEqualFunctionCode(expected: FunctionData, actual: ISharedFunction
|
|||||||
expect(actual.revertCode).to.equal(expected.revertCode);
|
expect(actual.revertCode).to.equal(expected.revertCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function areScrambledEqual(
|
||||||
|
expected: IReadOnlyFunctionParameterCollection,
|
||||||
|
actual: readonly ParameterDefinitionData[],
|
||||||
|
) {
|
||||||
|
if (expected.all.length !== actual.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const expectedParameter of expected.all) {
|
||||||
|
if (!actual.some(
|
||||||
|
(a) => a.name === expectedParameter.name
|
||||||
|
&& (a.optional || false) === expectedParameter.isOptional)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
class MockableFunctionCompiler extends FunctionCompiler {
|
class MockableFunctionCompiler extends FunctionCompiler {
|
||||||
constructor(functionCallCompiler: IFunctionCallCompiler = new FunctionCallCompilerStub()) {
|
constructor(functionCallCompiler: IFunctionCallCompiler = new FunctionCallCompilerStub()) {
|
||||||
super(functionCallCompiler);
|
super(functionCallCompiler);
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { FunctionParameter } from '@/application/Parser/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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { FunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection';
|
||||||
|
import { FunctionParameterStub } from '@tests/unit/stubs/FunctionParameterStub';
|
||||||
|
|
||||||
|
describe('FunctionParameterCollection', () => {
|
||||||
|
it('all returns added parameters as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const expected = [
|
||||||
|
new FunctionParameterStub().withName('1'),
|
||||||
|
new FunctionParameterStub().withName('2').withOptionality(true),
|
||||||
|
new FunctionParameterStub().withName('3').withOptionality(false),
|
||||||
|
];
|
||||||
|
const sut = new FunctionParameterCollection();
|
||||||
|
for (const parameter of expected) {
|
||||||
|
sut.addParameter(parameter);
|
||||||
|
}
|
||||||
|
// act
|
||||||
|
const actual = sut.all;
|
||||||
|
// assert
|
||||||
|
expect(expected).to.deep.equal(actual);
|
||||||
|
});
|
||||||
|
it('throws when function parameters have same names', () => {
|
||||||
|
// arrange
|
||||||
|
const parameterName = 'duplicate-parameter';
|
||||||
|
const expectedError = `duplicate parameter name: "${parameterName}"`;
|
||||||
|
const sut = new FunctionParameterCollection();
|
||||||
|
sut.addParameter(new FunctionParameterStub().withName(parameterName));
|
||||||
|
// act
|
||||||
|
const act = () =>
|
||||||
|
sut.addParameter(new FunctionParameterStub().withName(parameterName));
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
describe('addParameter', () => {
|
||||||
|
it('throws if parameter is undefined', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'undefined parameter';
|
||||||
|
const value = undefined;
|
||||||
|
const sut = new FunctionParameterCollection();
|
||||||
|
// act
|
||||||
|
const act = () => sut.addParameter(value);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'mocha';
|
import 'mocha';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { SharedFunction } from '@/application/Parser/Script/Compiler/Function/SharedFunction';
|
import { SharedFunction } from '@/application/Parser/Script/Compiler/Function/SharedFunction';
|
||||||
|
import { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
|
||||||
|
import { FunctionParameterCollectionStub } from '@tests/unit/stubs/FunctionParameterCollectionStub';
|
||||||
|
|
||||||
describe('SharedFunction', () => {
|
describe('SharedFunction', () => {
|
||||||
describe('name', () => {
|
describe('name', () => {
|
||||||
@@ -31,25 +33,25 @@ describe('SharedFunction', () => {
|
|||||||
describe('parameters', () => {
|
describe('parameters', () => {
|
||||||
it('sets as expected', () => {
|
it('sets as expected', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const expected = [ 'expected-parameter' ];
|
const expected = new FunctionParameterCollectionStub()
|
||||||
|
.withParameterName('test-parameter');
|
||||||
// act
|
// act
|
||||||
const sut = new SharedFunctionBuilder()
|
const sut = new SharedFunctionBuilder()
|
||||||
.withParameters(expected)
|
.withParameters(expected)
|
||||||
.build();
|
.build();
|
||||||
// assert
|
// assert
|
||||||
expect(sut.parameters).to.deep.equal(expected);
|
expect(sut.parameters).to.equal(expected);
|
||||||
});
|
});
|
||||||
it('returns empty array if undefined', () => {
|
it('throws if undefined', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const expected = [ ];
|
const expectedError = 'undefined parameters';
|
||||||
const value = undefined;
|
const parameters = undefined;
|
||||||
// act
|
// act
|
||||||
const sut = new SharedFunctionBuilder()
|
const act = () => new SharedFunctionBuilder()
|
||||||
.withParameters(value)
|
.withParameters(parameters)
|
||||||
.build();
|
.build();
|
||||||
// assert
|
// assert
|
||||||
expect(sut.parameters).to.not.equal(undefined);
|
expect(act).to.throw(expectedError);
|
||||||
expect(sut.parameters).to.deep.equal(expected);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('code', () => {
|
describe('code', () => {
|
||||||
@@ -97,7 +99,7 @@ describe('SharedFunction', () => {
|
|||||||
|
|
||||||
class SharedFunctionBuilder {
|
class SharedFunctionBuilder {
|
||||||
private name = 'name';
|
private name = 'name';
|
||||||
private parameters: readonly string[] = [ 'parameter' ];
|
private parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub();
|
||||||
private code = 'code';
|
private code = 'code';
|
||||||
private revertCode = 'revert-code';
|
private revertCode = 'revert-code';
|
||||||
|
|
||||||
@@ -113,7 +115,7 @@ class SharedFunctionBuilder {
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public withParameters(parameters: readonly string[]) {
|
public withParameters(parameters: IReadOnlyFunctionParameterCollection) {
|
||||||
this.parameters = parameters;
|
this.parameters = parameters;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/FunctionCallArgument';
|
||||||
|
import { testParameterName } from '../../ParameterNameTestRunner';
|
||||||
|
|
||||||
|
describe('FunctionCallArgument', () => {
|
||||||
|
describe('ctor', () => {
|
||||||
|
describe('parameter name', () => {
|
||||||
|
testParameterName(
|
||||||
|
(parameterName) => new FunctionCallArgumentBuilder()
|
||||||
|
.withParameterName(parameterName)
|
||||||
|
.build()
|
||||||
|
.parameterName,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('throws if argument value is undefined', () => {
|
||||||
|
// arrange
|
||||||
|
const parameterName = 'paramName';
|
||||||
|
const expectedError = `undefined argument value for "${parameterName}"`;
|
||||||
|
const argumentValue = undefined;
|
||||||
|
// act
|
||||||
|
const act = () => new FunctionCallArgumentBuilder()
|
||||||
|
.withParameterName(parameterName)
|
||||||
|
.withArgumentValue(argumentValue)
|
||||||
|
.build();
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
class FunctionCallArgumentBuilder {
|
||||||
|
private parameterName = 'default-parameter-name';
|
||||||
|
private argumentValue = 'default-argument-value';
|
||||||
|
public withParameterName(parameterName: string) {
|
||||||
|
this.parameterName = parameterName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public withArgumentValue(argumentValue: string) {
|
||||||
|
this.argumentValue = argumentValue;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public build() {
|
||||||
|
return new FunctionCallArgument(this.parameterName, this.argumentValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/FunctionCallArgumentCollection';
|
||||||
|
import { FunctionCallArgumentStub } from '@tests/unit/stubs/FunctionCallArgumentStub';
|
||||||
|
|
||||||
|
describe('FunctionCallArgumentCollection', () => {
|
||||||
|
describe('addArgument', () => {
|
||||||
|
it('throws if argument is undefined', () => {
|
||||||
|
// arrange
|
||||||
|
const errorMessage = 'undefined argument';
|
||||||
|
const arg = undefined;
|
||||||
|
const sut = new FunctionCallArgumentCollection();
|
||||||
|
// act
|
||||||
|
const act = () => sut.addArgument(arg);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(errorMessage);
|
||||||
|
});
|
||||||
|
it('throws if parameter value is already provided', () => {
|
||||||
|
// arrange
|
||||||
|
const duplicateParameterName = 'duplicateParam';
|
||||||
|
const errorMessage = `argument value for parameter ${duplicateParameterName} is already provided`;
|
||||||
|
const arg1 = new FunctionCallArgumentStub().withParameterName(duplicateParameterName);
|
||||||
|
const arg2 = new FunctionCallArgumentStub().withParameterName(duplicateParameterName);
|
||||||
|
const sut = new FunctionCallArgumentCollection();
|
||||||
|
// act
|
||||||
|
sut.addArgument(arg1);
|
||||||
|
const act = () => sut.addArgument(arg2);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(errorMessage);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('getAllParameterNames', () => {
|
||||||
|
it('returns as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const testCases = [ {
|
||||||
|
name: 'no args',
|
||||||
|
args: [],
|
||||||
|
expected: [],
|
||||||
|
}, {
|
||||||
|
name: 'with some args',
|
||||||
|
args: [
|
||||||
|
new FunctionCallArgumentStub().withParameterName('a-param-name'),
|
||||||
|
new FunctionCallArgumentStub().withParameterName('b-param-name')],
|
||||||
|
expected: [ 'a-param-name', 'b-param-name'],
|
||||||
|
}];
|
||||||
|
for (const testCase of testCases) {
|
||||||
|
it(testCase.name, () => {
|
||||||
|
const sut = new FunctionCallArgumentCollection();
|
||||||
|
// act
|
||||||
|
for (const arg of testCase.args) {
|
||||||
|
sut.addArgument(arg);
|
||||||
|
}
|
||||||
|
const actual = sut.getAllParameterNames();
|
||||||
|
// assert
|
||||||
|
expect(actual).to.equal(testCase.expected);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('getArgument', () => {
|
||||||
|
it('throws if parameter name is undefined', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'undefined parameter name';
|
||||||
|
const undefinedValues = [ '', undefined ];
|
||||||
|
for (const undefinedValue of undefinedValues) {
|
||||||
|
const sut = new FunctionCallArgumentCollection();
|
||||||
|
// act
|
||||||
|
const act = () => sut.getArgument(undefinedValue);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('throws if argument does not exist', () => {
|
||||||
|
// arrange
|
||||||
|
const parameterName = 'nonExistingParam';
|
||||||
|
const expectedError = `parameter does not exist: ${parameterName}`;
|
||||||
|
const sut = new FunctionCallArgumentCollection();
|
||||||
|
// act
|
||||||
|
const act = () => sut.getArgument(parameterName);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
it('returns argument as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const expected = new FunctionCallArgumentStub()
|
||||||
|
.withParameterName('expectedName')
|
||||||
|
.withArgumentValue('expectedValue');
|
||||||
|
const sut = new FunctionCallArgumentCollection();
|
||||||
|
// act
|
||||||
|
sut.addArgument(expected);
|
||||||
|
const actual = sut.getArgument(expected.parameterName);
|
||||||
|
// assert
|
||||||
|
expect(actual).to.equal(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('hasArgument', () => {
|
||||||
|
it('throws if parameter name is undefined', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'undefined parameter name';
|
||||||
|
const undefinedValues = [ '', undefined ];
|
||||||
|
for (const undefinedValue of undefinedValues) {
|
||||||
|
const sut = new FunctionCallArgumentCollection();
|
||||||
|
// act
|
||||||
|
const act = () => sut.hasArgument(undefinedValue);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
describe('returns as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const testCases = [ {
|
||||||
|
name: 'argument exists',
|
||||||
|
parameter: 'existing-parameter-name',
|
||||||
|
args: [
|
||||||
|
new FunctionCallArgumentStub().withParameterName('existing-parameter-name'),
|
||||||
|
new FunctionCallArgumentStub().withParameterName('unrelated-parameter-name'),
|
||||||
|
],
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'argument does not exist',
|
||||||
|
parameter: 'not-existing-parameter-name',
|
||||||
|
args: [
|
||||||
|
new FunctionCallArgumentStub().withParameterName('unrelated-parameter-name-b'),
|
||||||
|
new FunctionCallArgumentStub().withParameterName('unrelated-parameter-name-a'),
|
||||||
|
],
|
||||||
|
expected: false,
|
||||||
|
}];
|
||||||
|
for (const testCase of testCases) {
|
||||||
|
it(`"${testCase.name}" returns "${testCase.expected}"`, () => {
|
||||||
|
const sut = new FunctionCallArgumentCollection();
|
||||||
|
// act
|
||||||
|
for (const arg of testCase.args) {
|
||||||
|
sut.addArgument(arg);
|
||||||
|
}
|
||||||
|
const actual = sut.hasArgument(testCase.parameter);
|
||||||
|
// assert
|
||||||
|
expect(actual).to.equal(testCase.expected);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { FunctionCall } from '@/application/Parser/Script/Compiler/FunctionCall/FunctionCall';
|
||||||
|
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/IFunctionCallArgumentCollection';
|
||||||
|
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
||||||
|
|
||||||
|
describe('FunctionCall', () => {
|
||||||
|
describe('ctor', () => {
|
||||||
|
it('throws when args is undefined', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'undefined args';
|
||||||
|
const args = undefined;
|
||||||
|
// act
|
||||||
|
const act = () => new FunctionCallBuilder()
|
||||||
|
.withArgs(args)
|
||||||
|
.build();
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
it('throws when function name is undefined', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'empty function name in function call';
|
||||||
|
const functionName = undefined;
|
||||||
|
// act
|
||||||
|
const act = () => new FunctionCallBuilder()
|
||||||
|
.withFunctionName(functionName)
|
||||||
|
.build();
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
class FunctionCallBuilder {
|
||||||
|
private functionName = 'functionName';
|
||||||
|
private args: IReadOnlyFunctionCallArgumentCollection = new FunctionCallArgumentCollectionStub();
|
||||||
|
|
||||||
|
public withFunctionName(functionName: string) {
|
||||||
|
this.functionName = functionName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public withArgs(args: IReadOnlyFunctionCallArgumentCollection) {
|
||||||
|
this.args = args;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public build() {
|
||||||
|
return new FunctionCall(this.functionName, this.args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expre
|
|||||||
import { ExpressionsCompilerStub } from '@tests/unit/stubs/ExpressionsCompilerStub';
|
import { ExpressionsCompilerStub } from '@tests/unit/stubs/ExpressionsCompilerStub';
|
||||||
import { SharedFunctionCollectionStub } from '@tests/unit/stubs/SharedFunctionCollectionStub';
|
import { SharedFunctionCollectionStub } from '@tests/unit/stubs/SharedFunctionCollectionStub';
|
||||||
import { SharedFunctionStub } from '@tests/unit/stubs/SharedFunctionStub';
|
import { SharedFunctionStub } from '@tests/unit/stubs/SharedFunctionStub';
|
||||||
|
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
||||||
|
|
||||||
describe('FunctionCallCompiler', () => {
|
describe('FunctionCallCompiler', () => {
|
||||||
describe('compileCall', () => {
|
describe('compileCall', () => {
|
||||||
@@ -52,7 +53,7 @@ describe('FunctionCallCompiler', () => {
|
|||||||
});
|
});
|
||||||
it('throws if call sequence has undefined function name', () => {
|
it('throws if call sequence has undefined function name', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const expectedError = 'empty function name called';
|
const expectedError = 'empty function name in function call';
|
||||||
const call: FunctionCallData[] = [
|
const call: FunctionCallData[] = [
|
||||||
{ function: 'function-name' },
|
{ function: 'function-name' },
|
||||||
{ function: undefined },
|
{ function: undefined },
|
||||||
@@ -91,13 +92,14 @@ describe('FunctionCallCompiler', () => {
|
|||||||
it(testCase.name, () => {
|
it(testCase.name, () => {
|
||||||
const func = new SharedFunctionStub()
|
const func = new SharedFunctionStub()
|
||||||
.withName('test-function-name')
|
.withName('test-function-name')
|
||||||
.withParameters(...testCase.functionParameters);
|
.withParameterNames(...testCase.functionParameters);
|
||||||
let params: FunctionCallParametersData = {};
|
let params: FunctionCallParametersData = {};
|
||||||
for (const parameter of testCase.callParameters) {
|
for (const parameter of testCase.callParameters) {
|
||||||
params = {...params, [parameter]: 'defined-parameter-value '};
|
params = {...params, [parameter]: 'defined-parameter-value '};
|
||||||
}
|
}
|
||||||
const call: FunctionCallData = { function: func.name, parameters: params };
|
const call: FunctionCallData = { function: func.name, parameters: params };
|
||||||
const functions = new SharedFunctionCollectionStub().withFunction(func);
|
const functions = new SharedFunctionCollectionStub()
|
||||||
|
.withFunction(func);
|
||||||
const sut = new MockableFunctionCallCompiler();
|
const sut = new MockableFunctionCallCompiler();
|
||||||
// act
|
// act
|
||||||
const act = () => sut.compileCall(call, functions);
|
const act = () => sut.compileCall(call, functions);
|
||||||
@@ -134,38 +136,33 @@ describe('FunctionCallCompiler', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
describe('builds code as expected', () => {
|
describe('builds code as expected', () => {
|
||||||
describe('builds single call as expected', () => {
|
describe('builds single call as expected', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const parametersTestCases = [
|
const parametersTestCases = [
|
||||||
{
|
|
||||||
name: 'undefined parameters',
|
|
||||||
parameters: undefined,
|
|
||||||
parameterValues: undefined,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'empty parameters',
|
name: 'empty parameters',
|
||||||
parameters: [],
|
parameters: [],
|
||||||
parameterValues: { },
|
callArgs: { },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'non-empty parameters',
|
name: 'non-empty parameters',
|
||||||
parameters: [ 'param1', 'param2' ],
|
parameters: [ 'param1', 'param2' ],
|
||||||
parameterValues: { param1: 'value1', param2: 'value2' },
|
callArgs: { param1: 'value1', param2: 'value2' },
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
for (const testCase of parametersTestCases) {
|
for (const testCase of parametersTestCases) {
|
||||||
it(testCase.name, () => {
|
it(testCase.name, () => {
|
||||||
const expectedExecute = `expected-execute`;
|
const expectedExecute = `expected-execute`;
|
||||||
const expectedRevert = `expected-revert`;
|
const expectedRevert = `expected-revert`;
|
||||||
const func = new SharedFunctionStub().withParameters(...testCase.parameters);
|
const func = new SharedFunctionStub().withParameterNames(...testCase.parameters);
|
||||||
const functions = new SharedFunctionCollectionStub().withFunction(func);
|
const functions = new SharedFunctionCollectionStub().withFunction(func);
|
||||||
const call: FunctionCallData = { function: func.name, parameters: testCase.parameterValues };
|
const call: FunctionCallData = { function: func.name, parameters: testCase.callArgs };
|
||||||
|
const args = new FunctionCallArgumentCollectionStub().withArguments(testCase.callArgs);
|
||||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||||
.setup(func.code, testCase.parameterValues, expectedExecute)
|
.setup(func.code, args, expectedExecute)
|
||||||
.setup(func.revertCode, testCase.parameterValues, expectedRevert);
|
.setup(func.revertCode, args, expectedRevert);
|
||||||
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||||
// act
|
// act
|
||||||
const actual = sut.compileCall(call, functions);
|
const actual = sut.compileCall(call, functions);
|
||||||
@@ -183,7 +180,7 @@ describe('FunctionCallCompiler', () => {
|
|||||||
.withRevertCode('first-function-revert-code');
|
.withRevertCode('first-function-revert-code');
|
||||||
const secondFunction = new SharedFunctionStub()
|
const secondFunction = new SharedFunctionStub()
|
||||||
.withName('second-function-name')
|
.withName('second-function-name')
|
||||||
.withParameters('testParameter')
|
.withParameterNames('testParameter')
|
||||||
.withCode('second-function-code')
|
.withCode('second-function-code')
|
||||||
.withRevertCode('second-function-revert-code');
|
.withRevertCode('second-function-revert-code');
|
||||||
const secondCallArguments = { testParameter: 'testValue' };
|
const secondCallArguments = { testParameter: 'testValue' };
|
||||||
@@ -191,11 +188,14 @@ describe('FunctionCallCompiler', () => {
|
|||||||
{ function: firstFunction.name },
|
{ function: firstFunction.name },
|
||||||
{ function: secondFunction.name, parameters: secondCallArguments },
|
{ function: secondFunction.name, parameters: secondCallArguments },
|
||||||
];
|
];
|
||||||
|
const firstFunctionCallArgs = new FunctionCallArgumentCollectionStub();
|
||||||
|
const secondFunctionCallArgs = new FunctionCallArgumentCollectionStub()
|
||||||
|
.withArguments(secondCallArguments);
|
||||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||||
.setup(firstFunction.code, {}, firstFunction.code)
|
.setup(firstFunction.code, firstFunctionCallArgs, firstFunction.code)
|
||||||
.setup(firstFunction.revertCode, {}, firstFunction.revertCode)
|
.setup(firstFunction.revertCode, firstFunctionCallArgs, firstFunction.revertCode)
|
||||||
.setup(secondFunction.code, secondCallArguments, secondFunction.code)
|
.setup(secondFunction.code, secondFunctionCallArgs, secondFunction.code)
|
||||||
.setup(secondFunction.revertCode, secondCallArguments, secondFunction.revertCode);
|
.setup(secondFunction.revertCode, secondFunctionCallArgs, secondFunction.revertCode);
|
||||||
const expectedExecute = `${firstFunction.code}\n${secondFunction.code}`;
|
const expectedExecute = `${firstFunction.code}\n${secondFunction.code}`;
|
||||||
const expectedRevert = `${firstFunction.revertCode}\n${secondFunction.revertCode}`;
|
const expectedRevert = `${firstFunction.revertCode}\n${secondFunction.revertCode}`;
|
||||||
const functions = new SharedFunctionCollectionStub()
|
const functions = new SharedFunctionCollectionStub()
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
|
||||||
|
export function testParameterName(action: (parameterName: string) => string) {
|
||||||
|
describe('name', () => {
|
||||||
|
describe('sets as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedValues = [
|
||||||
|
'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 testCases = [
|
||||||
|
{
|
||||||
|
name: 'undefined',
|
||||||
|
value: undefined,
|
||||||
|
expectedError: 'undefined parameter name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'empty',
|
||||||
|
value: '',
|
||||||
|
expectedError: 'undefined parameter name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'has @',
|
||||||
|
value: 'b@d',
|
||||||
|
expectedError: 'parameter name must be alphanumeric but it was "b@d"',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'has {',
|
||||||
|
value: 'b{a}d',
|
||||||
|
expectedError: 'parameter name must be alphanumeric but it was "b{a}d"',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
for (const testCase of testCases) {
|
||||||
|
it(testCase.name, () => {
|
||||||
|
// act
|
||||||
|
const act = () => action(testCase.value);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(testCase.expectedError);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -60,7 +60,10 @@ describe('CodeSubstituter', () => {
|
|||||||
sut.substitute('non empty code', info);
|
sut.substitute('non empty code', info);
|
||||||
// assert
|
// assert
|
||||||
expect(compilerStub.callHistory).to.have.lengthOf(1);
|
expect(compilerStub.callHistory).to.have.lengthOf(1);
|
||||||
expect(compilerStub.callHistory[0].parameters[testCase.parameter]).to.equal(testCase.argument);
|
const parameters = compilerStub.callHistory[0].parameters;
|
||||||
|
expect(parameters.hasArgument(testCase.parameter));
|
||||||
|
const argumentValue = parameters.getArgument(testCase.parameter).argumentValue;
|
||||||
|
expect(argumentValue).to.equal(testCase.argument);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,15 +1,23 @@
|
|||||||
import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition';
|
import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition';
|
||||||
import { ExpressionArguments, IExpression } from '@/application/Parser/Script/Compiler/Expressions/Expression/IExpression';
|
import { IExpression } from '@/application/Parser/Script/Compiler/Expressions/Expression/IExpression';
|
||||||
|
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/IFunctionCallArgumentCollection';
|
||||||
|
import { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
|
||||||
|
import { FunctionParameterCollectionStub } from './FunctionParameterCollectionStub';
|
||||||
|
|
||||||
export class ExpressionStub implements IExpression {
|
export class ExpressionStub implements IExpression {
|
||||||
public callHistory = new Array<ExpressionArguments>();
|
public callHistory = new Array<IReadOnlyFunctionCallArgumentCollection>();
|
||||||
public position = new ExpressionPosition(0, 5);
|
public position = new ExpressionPosition(0, 5);
|
||||||
public parameters = [];
|
public parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub();
|
||||||
private result: string;
|
private result: string;
|
||||||
public withParameters(...parameters: string[]) {
|
public withParameters(parameters: IReadOnlyFunctionParameterCollection) {
|
||||||
this.parameters = parameters;
|
this.parameters = parameters;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
public withParameterNames(parameterNames: readonly string[], isOptional = false) {
|
||||||
|
const collection = new FunctionParameterCollectionStub()
|
||||||
|
.withParameterNames(parameterNames, isOptional);
|
||||||
|
return this.withParameters(collection);
|
||||||
|
}
|
||||||
public withPosition(start: number, end: number) {
|
public withPosition(start: number, end: number) {
|
||||||
this.position = new ExpressionPosition(start, end);
|
this.position = new ExpressionPosition(start, end);
|
||||||
return this;
|
return this;
|
||||||
@@ -18,7 +26,7 @@ export class ExpressionStub implements IExpression {
|
|||||||
this.result = result;
|
this.result = result;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public evaluate(args?: ExpressionArguments): string {
|
public evaluate(args: IReadOnlyFunctionCallArgumentCollection): string {
|
||||||
this.callHistory.push(args);
|
this.callHistory.push(args);
|
||||||
const result = this.result || `[expression-stub] args: ${args ? Object.keys(args).map((key) => `${key}: ${args[key]}`).join('", "') : 'none'}`;
|
const result = this.result || `[expression-stub] args: ${args ? Object.keys(args).map((key) => `${key}: ${args[key]}`).join('", "') : 'none'}`;
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -1,30 +1,56 @@
|
|||||||
import { IExpressionsCompiler, ParameterValueDictionary } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
|
import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
|
||||||
|
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/IFunctionCallArgumentCollection';
|
||||||
|
import { scrambledEqual } from '@/application/Common/Array';
|
||||||
|
|
||||||
interface Scenario { code: string; parameters: ParameterValueDictionary; result: string; }
|
|
||||||
|
|
||||||
export class ExpressionsCompilerStub implements IExpressionsCompiler {
|
export class ExpressionsCompilerStub implements IExpressionsCompiler {
|
||||||
public readonly callHistory = new Array<{code: string, parameters?: ParameterValueDictionary}>();
|
public readonly callHistory = new Array<{code: string, parameters: IReadOnlyFunctionCallArgumentCollection}>();
|
||||||
private readonly scenarios = new Array<Scenario>();
|
|
||||||
public setup(code: string, parameters: ParameterValueDictionary, result: string) {
|
private readonly scenarios = new Array<ITestScenario>();
|
||||||
|
|
||||||
|
public setup(
|
||||||
|
code: string,
|
||||||
|
parameters: IReadOnlyFunctionCallArgumentCollection,
|
||||||
|
result: string): ExpressionsCompilerStub {
|
||||||
this.scenarios.push({ code, parameters, result });
|
this.scenarios.push({ code, parameters, result });
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public compileExpressions(code: string, parameters?: ParameterValueDictionary): string {
|
public compileExpressions(
|
||||||
|
code: string,
|
||||||
|
parameters: IReadOnlyFunctionCallArgumentCollection): string {
|
||||||
this.callHistory.push({ code, parameters});
|
this.callHistory.push({ code, parameters});
|
||||||
const scenario = this.scenarios.find((s) => s.code === code && deepEqual(s.parameters, parameters));
|
const scenario = this.scenarios.find((s) => s.code === code && deepEqual(s.parameters, parameters));
|
||||||
if (scenario) {
|
if (scenario) {
|
||||||
return scenario.result;
|
return scenario.result;
|
||||||
}
|
}
|
||||||
return `[ExpressionsCompilerStub] code: "${code}"` +
|
const parametersAndValues = parameters
|
||||||
`| parameters: ${Object.keys(parameters || {}).map((p) => p + '=' + parameters[p]).join(',')}`;
|
.getAllParameterNames()
|
||||||
|
.map((name) => `${name}=${parameters.getArgument(name).argumentValue}`)
|
||||||
|
.join('", "');
|
||||||
|
return `[ExpressionsCompilerStub] code: "${code}" | parameters: "${parametersAndValues}"`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function deepEqual(dict1: ParameterValueDictionary, dict2: ParameterValueDictionary) {
|
interface ITestScenario {
|
||||||
const dict1Keys = Object.keys(dict1 || {});
|
readonly code: string;
|
||||||
const dict2Keys = Object.keys(dict2 || {});
|
readonly parameters: IReadOnlyFunctionCallArgumentCollection;
|
||||||
if (dict1Keys.length !== dict2Keys.length) {
|
readonly result: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deepEqual(
|
||||||
|
expected: IReadOnlyFunctionCallArgumentCollection,
|
||||||
|
actual: IReadOnlyFunctionCallArgumentCollection): boolean {
|
||||||
|
const expectedParameterNames = expected.getAllParameterNames();
|
||||||
|
const actualParameterNames = actual.getAllParameterNames();
|
||||||
|
if (!scrambledEqual(expectedParameterNames, actualParameterNames)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return dict1Keys.every((key) => dict2.hasOwnProperty(key) && dict2[key] === dict1[key]);
|
for (const parameterName of expectedParameterNames) {
|
||||||
|
const expectedValue = expected.getArgument(parameterName);
|
||||||
|
const actualValue = expected.getArgument(parameterName);
|
||||||
|
if (expectedValue !== actualValue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
39
tests/unit/stubs/FunctionCallArgumentCollectionStub.ts
Normal file
39
tests/unit/stubs/FunctionCallArgumentCollectionStub.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// tslint:disable-next-line:max-line-length
|
||||||
|
import { IFunctionCallArgument } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/IFunctionCallArgument';
|
||||||
|
import { IFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/IFunctionCallArgumentCollection';
|
||||||
|
import { FunctionCallArgumentStub } from './FunctionCallArgumentStub';
|
||||||
|
|
||||||
|
export class FunctionCallArgumentCollectionStub implements IFunctionCallArgumentCollection {
|
||||||
|
private args = new Array<IFunctionCallArgument>();
|
||||||
|
|
||||||
|
public withArgument(parameterName: string, argumentValue: string) {
|
||||||
|
const arg = new FunctionCallArgumentStub()
|
||||||
|
.withParameterName(parameterName)
|
||||||
|
.withArgumentValue(argumentValue);
|
||||||
|
this.addArgument(arg);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public withArguments(args: { readonly [index: string]: string }) {
|
||||||
|
for (const parameterName of Object.keys(args)) {
|
||||||
|
const parameterValue = args[parameterName];
|
||||||
|
this.withArgument(parameterName, parameterValue);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public hasArgument(parameterName: string): boolean {
|
||||||
|
return this.args.some((a) => a.parameterName === parameterName);
|
||||||
|
}
|
||||||
|
public addArgument(argument: IFunctionCallArgument): void {
|
||||||
|
this.args.push(argument);
|
||||||
|
}
|
||||||
|
public getAllParameterNames(): string[] {
|
||||||
|
return this.args.map((a) => a.parameterName);
|
||||||
|
}
|
||||||
|
public getArgument(parameterName: string): IFunctionCallArgument {
|
||||||
|
const arg = this.args.find((a) => a.parameterName === parameterName);
|
||||||
|
if (!arg) {
|
||||||
|
throw new Error(`no argument exists for parameter "${parameterName}"`);
|
||||||
|
}
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
15
tests/unit/stubs/FunctionCallArgumentStub.ts
Normal file
15
tests/unit/stubs/FunctionCallArgumentStub.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// tslint:disable-next-line:max-line-length
|
||||||
|
import { IFunctionCallArgument } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/IFunctionCallArgument';
|
||||||
|
|
||||||
|
export class FunctionCallArgumentStub implements IFunctionCallArgument {
|
||||||
|
public parameterName = 'stub-parameter-name';
|
||||||
|
public argumentValue = 'stub-arg-name';
|
||||||
|
public withParameterName(parameterName: string) {
|
||||||
|
this.parameterName = parameterName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public withArgumentValue(argumentValue: string) {
|
||||||
|
this.argumentValue = argumentValue;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FunctionData, ScriptFunctionCallData } from 'js-yaml-loader!@/*';
|
import { FunctionData, ParameterDefinitionData, ScriptFunctionCallData } from 'js-yaml-loader!@/*';
|
||||||
|
|
||||||
export class FunctionDataStub implements FunctionData {
|
export class FunctionDataStub implements FunctionData {
|
||||||
public static createWithCode() {
|
public static createWithCode() {
|
||||||
@@ -19,11 +19,11 @@ export class FunctionDataStub implements FunctionData {
|
|||||||
return new FunctionDataStub();
|
return new FunctionDataStub();
|
||||||
}
|
}
|
||||||
|
|
||||||
public name = 'function data stub';
|
public name = 'functionDataStub';
|
||||||
public code: string;
|
public code: string;
|
||||||
public revertCode: string;
|
public revertCode: string;
|
||||||
public parameters?: readonly string[];
|
|
||||||
public call?: ScriptFunctionCallData;
|
public call?: ScriptFunctionCallData;
|
||||||
|
public parameters?: readonly ParameterDefinitionData[];
|
||||||
|
|
||||||
private constructor() { }
|
private constructor() { }
|
||||||
|
|
||||||
@@ -31,7 +31,10 @@ export class FunctionDataStub implements FunctionData {
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public withParameters(...parameters: string[]) {
|
public withParameters(...parameters: readonly ParameterDefinitionData[]) {
|
||||||
|
return this.withParametersObject(parameters);
|
||||||
|
}
|
||||||
|
public withParametersObject(parameters: readonly ParameterDefinitionData[]) {
|
||||||
this.parameters = parameters;
|
this.parameters = parameters;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
27
tests/unit/stubs/FunctionParameterCollectionStub.ts
Normal file
27
tests/unit/stubs/FunctionParameterCollectionStub.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { IFunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameter';
|
||||||
|
import { IFunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
|
||||||
|
import { FunctionParameterStub } from './FunctionParameterStub';
|
||||||
|
|
||||||
|
export class FunctionParameterCollectionStub implements IFunctionParameterCollection {
|
||||||
|
private parameters = new Array<IFunctionParameter>();
|
||||||
|
|
||||||
|
public addParameter(parameter: IFunctionParameter): void {
|
||||||
|
this.parameters.push(parameter);
|
||||||
|
}
|
||||||
|
public get all(): readonly IFunctionParameter[] {
|
||||||
|
return this.parameters;
|
||||||
|
}
|
||||||
|
public withParameterName(parameterName: string, isOptional = true) {
|
||||||
|
const parameter = new FunctionParameterStub()
|
||||||
|
.withName(parameterName)
|
||||||
|
.withOptionality(isOptional);
|
||||||
|
this.addParameter(parameter);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public withParameterNames(parameterNames: readonly string[], isOptional = true) {
|
||||||
|
for (const parameterName of parameterNames) {
|
||||||
|
this.withParameterName(parameterName, isOptional);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
14
tests/unit/stubs/FunctionParameterStub.ts
Normal file
14
tests/unit/stubs/FunctionParameterStub.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { IFunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameter';
|
||||||
|
|
||||||
|
export class FunctionParameterStub implements IFunctionParameter {
|
||||||
|
public name: string = 'function-parameter-stub';
|
||||||
|
public isOptional: boolean = true;
|
||||||
|
public withName(name: string) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public withOptionality(isOptional: boolean) {
|
||||||
|
this.isOptional = isOptional;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
14
tests/unit/stubs/ParameterDefinitionDataStub.ts
Normal file
14
tests/unit/stubs/ParameterDefinitionDataStub.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { ParameterDefinitionData } from 'js-yaml-loader!@/*';
|
||||||
|
|
||||||
|
export class ParameterDefinitionDataStub implements ParameterDefinitionData {
|
||||||
|
public name: string;
|
||||||
|
public optional?: boolean;
|
||||||
|
public withName(name: string) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public withOptionality(isOptional: boolean) {
|
||||||
|
this.optional = isOptional;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||||
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
||||||
|
import { SharedFunctionStub } from './SharedFunctionStub';
|
||||||
|
|
||||||
export class SharedFunctionCollectionStub implements ISharedFunctionCollection {
|
export class SharedFunctionCollectionStub implements ISharedFunctionCollection {
|
||||||
private readonly functions = new Map<string, ISharedFunction>();
|
private readonly functions = new Map<string, ISharedFunction>();
|
||||||
@@ -11,11 +12,9 @@ export class SharedFunctionCollectionStub implements ISharedFunctionCollection {
|
|||||||
if (this.functions.has(name)) {
|
if (this.functions.has(name)) {
|
||||||
return this.functions.get(name);
|
return this.functions.get(name);
|
||||||
}
|
}
|
||||||
return {
|
return new SharedFunctionStub()
|
||||||
name,
|
.withName(name)
|
||||||
parameters: [],
|
.withCode('code by SharedFunctionCollectionStub')
|
||||||
code: 'code by SharedFunctionCollectionStub',
|
.withRevertCode('revert-code by SharedFunctionCollectionStub');
|
||||||
revertCode: 'revert-code by SharedFunctionCollectionStub',
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||||
|
import { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
|
||||||
|
import { FunctionParameterCollectionStub } from './FunctionParameterCollectionStub';
|
||||||
|
|
||||||
export class SharedFunctionStub implements ISharedFunction {
|
export class SharedFunctionStub implements ISharedFunction {
|
||||||
public name = 'shared-function-stub-name';
|
public name = 'shared-function-stub-name';
|
||||||
public parameters?: readonly string[] = [
|
public parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub()
|
||||||
'shared-function-stub-parameter',
|
.withParameterName('shared-function-stub-parameter-name');
|
||||||
];
|
|
||||||
public code = 'shared-function-stub-code';
|
public code = 'shared-function-stub-code';
|
||||||
public revertCode = 'shared-function-stub-revert-code';
|
public revertCode = 'shared-function-stub-revert-code';
|
||||||
|
|
||||||
@@ -20,8 +21,15 @@ export class SharedFunctionStub implements ISharedFunction {
|
|||||||
this.revertCode = revertCode;
|
this.revertCode = revertCode;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public withParameters(...params: string[]) {
|
public withParameters(parameters: IReadOnlyFunctionParameterCollection) {
|
||||||
this.parameters = params;
|
this.parameters = parameters;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
public withParameterNames(...parameterNames: readonly string[]) {
|
||||||
|
let collection = new FunctionParameterCollectionStub();
|
||||||
|
for (const name of parameterNames) {
|
||||||
|
collection = collection.withParameterName(name);
|
||||||
|
}
|
||||||
|
return this.withParameters(collection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user