Refactor to improve iterations
- Use function abstractions (such as map, reduce, filter etc.) over for-of loops to gain benefits of having less side effects and easier readability. - Enable `downLevelIterations` for writing modern code with lazy evaluation. - Refactor for of loops to named abstractions to clearly express their intentions without needing to analyse the loop itself. - Add missing cases for changes that had no tests.
This commit is contained in:
@@ -56,14 +56,13 @@ function compileExpressions(
|
||||
function extractRequiredParameterNames(
|
||||
expressions: readonly IExpression[],
|
||||
): string[] {
|
||||
const usedParameterNames = expressions
|
||||
return expressions
|
||||
.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;
|
||||
.filter(Boolean) // Remove empty or undefined
|
||||
.flat()
|
||||
.filter((name, index, array) => array.indexOf(name) === index); // Remove duplicates
|
||||
}
|
||||
|
||||
function ensureParamsUsedInCodeHasArgsProvided(
|
||||
|
||||
@@ -16,13 +16,8 @@ export class CompositeExpressionParser implements IExpressionParser {
|
||||
}
|
||||
|
||||
public findExpressions(code: string): IExpression[] {
|
||||
const expressions = new Array<IExpression>();
|
||||
for (const parser of this.leafs) {
|
||||
const newExpressions = parser.findExpressions(code);
|
||||
if (newExpressions && newExpressions.length) {
|
||||
expressions.push(...newExpressions);
|
||||
}
|
||||
}
|
||||
return expressions;
|
||||
return this.leafs.flatMap(
|
||||
(parser) => parser.findExpressions(code) || [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,35 +18,42 @@ export abstract class RegexParser implements IExpressionParser {
|
||||
if (!code) {
|
||||
throw new Error('undefined code');
|
||||
}
|
||||
const matches = Array.from(code.matchAll(this.regex));
|
||||
const matches = code.matchAll(this.regex);
|
||||
for (const match of matches) {
|
||||
const startPos = match.index;
|
||||
const endPos = startPos + match[0].length;
|
||||
let position: ExpressionPosition;
|
||||
try {
|
||||
position = new ExpressionPosition(startPos, endPos);
|
||||
} catch (error) {
|
||||
throw new Error(`[${this.constructor.name}] invalid script position: ${error.message}\nRegex ${this.regex}\nCode: ${code}`);
|
||||
}
|
||||
const primitiveExpression = this.buildExpression(match);
|
||||
const parameters = getParameters(primitiveExpression);
|
||||
const position = this.doOrRethrow(() => createPosition(match), 'invalid script position', code);
|
||||
const parameters = createParameters(primitiveExpression);
|
||||
const expression = new Expression(position, primitiveExpression.evaluator, parameters);
|
||||
yield expression;
|
||||
}
|
||||
}
|
||||
|
||||
private doOrRethrow<T>(action: () => T, errorText: string, code: string): T {
|
||||
try {
|
||||
return action();
|
||||
} catch (error) {
|
||||
throw new Error(`[${this.constructor.name}] ${errorText}: ${error.message}\nRegex: ${this.regex}\nCode: ${code}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createPosition(match: RegExpMatchArray): ExpressionPosition {
|
||||
const startPos = match.index;
|
||||
const endPos = startPos + match[0].length;
|
||||
return new ExpressionPosition(startPos, endPos);
|
||||
}
|
||||
|
||||
function createParameters(
|
||||
expression: IPrimitiveExpression,
|
||||
): FunctionParameterCollection {
|
||||
return (expression.parameters || [])
|
||||
.reduce((parameters, parameter) => {
|
||||
parameters.addParameter(parameter);
|
||||
return parameters;
|
||||
}, new FunctionParameterCollection());
|
||||
}
|
||||
|
||||
export interface IPrimitiveExpression {
|
||||
evaluator: ExpressionEvaluator;
|
||||
parameters?: readonly IFunctionParameter[];
|
||||
}
|
||||
|
||||
function getParameters(
|
||||
expression: IPrimitiveExpression,
|
||||
): FunctionParameterCollection {
|
||||
const parameters = new FunctionParameterCollection();
|
||||
for (const parameter of expression.parameters || []) {
|
||||
parameters.addParameter(parameter);
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
@@ -8,11 +8,9 @@ export class PipelineCompiler implements IPipelineCompiler {
|
||||
ensureValidArguments(value, pipeline);
|
||||
const pipeNames = extractPipeNames(pipeline);
|
||||
const pipes = pipeNames.map((pipeName) => this.factory.get(pipeName));
|
||||
let valueInCompilation = value;
|
||||
for (const pipe of pipes) {
|
||||
valueInCompilation = pipe.apply(valueInCompilation);
|
||||
}
|
||||
return valueInCompilation;
|
||||
return pipes.reduce((previousValue, pipe) => {
|
||||
return pipe.apply(previousValue);
|
||||
}, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,11 +47,8 @@ interface ICompiledFunctionCall {
|
||||
}
|
||||
|
||||
function compileCallSequence(context: ICompilationContext): ICompiledFunctionCall {
|
||||
const compiledFunctions = new Array<ICompiledFunctionCall>();
|
||||
for (const call of context.callSequence) {
|
||||
const compiledCode = compileSingleCall(call, context);
|
||||
compiledFunctions.push(...compiledCode);
|
||||
}
|
||||
const compiledFunctions = context.callSequence
|
||||
.flatMap((call) => compileSingleCall(call, context));
|
||||
return {
|
||||
code: merge(compiledFunctions.map((f) => f.code)),
|
||||
revertCode: merge(compiledFunctions.map((f) => f.revertCode)),
|
||||
@@ -94,14 +91,17 @@ function compileArgs(
|
||||
args: IReadOnlyFunctionCallArgumentCollection,
|
||||
compiler: IExpressionsCompiler,
|
||||
): IReadOnlyFunctionCallArgumentCollection {
|
||||
const compiledArgs = new FunctionCallArgumentCollection();
|
||||
for (const parameterName of argsToCompile.getAllParameterNames()) {
|
||||
const { argumentValue } = argsToCompile.getArgument(parameterName);
|
||||
const compiledValue = compiler.compileExpressions(argumentValue, args);
|
||||
const newArgument = new FunctionCallArgument(parameterName, compiledValue);
|
||||
compiledArgs.addArgument(newArgument);
|
||||
}
|
||||
return compiledArgs;
|
||||
return argsToCompile
|
||||
.getAllParameterNames()
|
||||
.map((parameterName) => {
|
||||
const { argumentValue } = argsToCompile.getArgument(parameterName);
|
||||
const compiledValue = compiler.compileExpressions(argumentValue, args);
|
||||
return new FunctionCallArgument(parameterName, compiledValue);
|
||||
})
|
||||
.reduce((compiledArgs, arg) => {
|
||||
compiledArgs.addArgument(arg);
|
||||
return compiledArgs;
|
||||
}, new FunctionCallArgumentCollection());
|
||||
}
|
||||
|
||||
function merge(codeParts: readonly string[]): string {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FunctionCallData, FunctionCallsData } from 'js-yaml-loader!@/*';
|
||||
import { FunctionCallData, FunctionCallsData, FunctionCallParametersData } from 'js-yaml-loader!@/*';
|
||||
import { IFunctionCall } from './IFunctionCall';
|
||||
import { FunctionCallArgumentCollection } from './Argument/FunctionCallArgumentCollection';
|
||||
import { FunctionCallArgument } from './Argument/FunctionCallArgument';
|
||||
@@ -26,10 +26,17 @@ function parseFunctionCall(call: FunctionCallData): IFunctionCall {
|
||||
if (!call) {
|
||||
throw new Error('undefined function call');
|
||||
}
|
||||
const args = new FunctionCallArgumentCollection();
|
||||
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);
|
||||
const callArgs = parseArgs(call.parameters);
|
||||
return new FunctionCall(call.function, callArgs);
|
||||
}
|
||||
|
||||
function parseArgs(
|
||||
parameters: FunctionCallParametersData,
|
||||
): FunctionCallArgumentCollection {
|
||||
return Object.keys(parameters || {})
|
||||
.map((parameterName) => new FunctionCallArgument(parameterName, parameters[parameterName]))
|
||||
.reduce((args, arg) => {
|
||||
args.addArgument(arg);
|
||||
return args;
|
||||
}, new FunctionCallArgumentCollection());
|
||||
}
|
||||
|
||||
@@ -20,11 +20,12 @@ export class SharedFunctionsParser implements ISharedFunctionsParser {
|
||||
return collection;
|
||||
}
|
||||
ensureValidFunctions(functions);
|
||||
for (const func of functions) {
|
||||
const sharedFunction = parseFunction(func);
|
||||
collection.addFunction(sharedFunction);
|
||||
}
|
||||
return collection;
|
||||
return functions
|
||||
.map((func) => parseFunction(func))
|
||||
.reduce((acc, func) => {
|
||||
acc.addFunction(func);
|
||||
return acc;
|
||||
}, collection);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,20 +41,21 @@ function parseFunction(data: FunctionData): ISharedFunction {
|
||||
}
|
||||
|
||||
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);
|
||||
return (data.parameters || [])
|
||||
.map((parameter) => {
|
||||
try {
|
||||
return new FunctionParameter(
|
||||
parameter.name,
|
||||
parameter.optional || false,
|
||||
);
|
||||
} catch (err) {
|
||||
throw new Error(`"${data.name}": ${err.message}`);
|
||||
}
|
||||
})
|
||||
.reduce((parameters, parameter) => {
|
||||
parameters.addParameter(parameter);
|
||||
} catch (err) {
|
||||
throw new Error(`"${data.name}": ${err.message}`);
|
||||
}
|
||||
}
|
||||
return parameters;
|
||||
return parameters;
|
||||
}, new FunctionParameterCollection());
|
||||
}
|
||||
|
||||
function hasCode(data: FunctionData): boolean {
|
||||
|
||||
Reference in New Issue
Block a user