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:
undergroundwires
2022-01-04 21:45:22 +01:00
parent bd23faa28f
commit 31f70913a2
35 changed files with 342 additions and 343 deletions

View File

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

View File

@@ -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) || [],
);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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