Refactor to enforce strictNullChecks

This commit applies `strictNullChecks` to the entire codebase to improve
maintainability and type safety. Key changes include:

- Remove some explicit null-checks where unnecessary.
- Add necessary null-checks.
- Refactor static factory functions for a more functional approach.
- Improve some test names and contexts for better debugging.
- Add unit tests for any additional logic introduced.
- Refactor `createPositionFromRegexFullMatch` to its own function as the
  logic is reused.
- Prefer `find` prefix on functions that may return `undefined` and
  `get` prefix for those that always return a value.
This commit is contained in:
undergroundwires
2023-11-12 22:54:00 +01:00
parent 7ab16ecccb
commit 949fac1a7c
294 changed files with 2477 additions and 2738 deletions

View File

@@ -15,19 +15,10 @@ export class Expression implements IExpression {
public readonly evaluator: ExpressionEvaluator,
parameters?: IReadOnlyFunctionParameterCollection,
) {
if (!position) {
throw new Error('missing position');
}
if (!evaluator) {
throw new Error('missing evaluator');
}
this.parameters = parameters ?? new FunctionParameterCollection();
}
public evaluate(context: IExpressionEvaluationContext): string {
if (!context) {
throw new Error('missing context');
}
validateThatAllRequiredParametersAreSatisfied(this.parameters, context.args);
const args = filterUnusedArguments(this.parameters, context.args);
const filteredContext = new ExpressionEvaluationContext(args, context.pipelineCompiler);

View File

@@ -12,8 +12,5 @@ export class ExpressionEvaluationContext implements IExpressionEvaluationContext
public readonly args: IReadOnlyFunctionCallArgumentCollection,
public readonly pipelineCompiler: IPipelineCompiler = new PipelineCompiler(),
) {
if (!args) {
throw new Error('missing args, send empty collection instead.');
}
}
}

View File

@@ -0,0 +1,16 @@
import { ExpressionPosition } from './ExpressionPosition';
export function createPositionFromRegexFullMatch(
match: RegExpMatchArray,
): ExpressionPosition {
const startPos = match.index;
if (startPos === undefined) {
throw new Error(`Regex match did not yield any results: ${JSON.stringify(match)}`);
}
const fullMatch = match[0];
if (!fullMatch.length) {
throw new Error(`Regex match is empty: ${JSON.stringify(match)}`);
}
const endPos = startPos + fullMatch.length;
return new ExpressionPosition(startPos, endPos);
}

View File

@@ -11,14 +11,11 @@ export class ExpressionsCompiler implements IExpressionsCompiler {
) { }
public compileExpressions(
code: string | undefined,
code: string,
args: IReadOnlyFunctionCallArgumentCollection,
): string {
if (!args) {
throw new Error('missing args, send empty collection instead.');
}
if (!code) {
return code;
return '';
}
const context = new ExpressionEvaluationContext(args);
const compiledCode = compileRecursively(code, context, this.extractor);
@@ -145,7 +142,7 @@ function ensureParamsUsedInCodeHasArgsProvided(
providedArgs: IReadOnlyFunctionCallArgumentCollection,
): void {
const usedParameterNames = extractRequiredParameterNames(expressions);
if (!usedParameterNames?.length) {
if (!usedParameterNames.length) {
return;
}
const notProvidedParameters = usedParameterNames

View File

@@ -2,6 +2,7 @@ import { IReadOnlyFunctionCallArgumentCollection } from '../Function/Call/Argume
export interface IExpressionsCompiler {
compileExpressions(
code: string | undefined,
args: IReadOnlyFunctionCallArgumentCollection): string;
code: string,
args: IReadOnlyFunctionCallArgumentCollection,
): string;
}

View File

@@ -10,12 +10,9 @@ const Parsers = [
export class CompositeExpressionParser implements IExpressionParser {
public constructor(private readonly leafs: readonly IExpressionParser[] = Parsers) {
if (!leafs) {
if (!leafs.length) {
throw new Error('missing leafs');
}
if (leafs.some((leaf) => !leaf)) {
throw new Error('missing leaf');
}
}
public findExpressions(code: string): IExpression[] {

View File

@@ -1,9 +1,9 @@
import { IExpressionParser } from '../IExpressionParser';
import { ExpressionPosition } from '../../Expression/ExpressionPosition';
import { IExpression } from '../../Expression/IExpression';
import { Expression, ExpressionEvaluator } from '../../Expression/Expression';
import { IFunctionParameter } from '../../../Function/Parameter/IFunctionParameter';
import { FunctionParameterCollection } from '../../../Function/Parameter/FunctionParameterCollection';
import { createPositionFromRegexFullMatch } from '../../Expression/ExpressionPositionFactory';
export abstract class RegexParser implements IExpressionParser {
protected abstract readonly regex: RegExp;
@@ -21,7 +21,7 @@ export abstract class RegexParser implements IExpressionParser {
const matches = code.matchAll(this.regex);
for (const match of matches) {
const primitiveExpression = this.buildExpression(match);
const position = this.doOrRethrow(() => createPosition(match), 'invalid script position', code);
const position = this.doOrRethrow(() => createPositionFromRegexFullMatch(match), 'invalid script position', code);
const parameters = createParameters(primitiveExpression);
const expression = new Expression(position, primitiveExpression.evaluator, parameters);
yield expression;
@@ -37,12 +37,6 @@ export abstract class RegexParser implements IExpressionParser {
}
}
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 {

View File

@@ -28,7 +28,7 @@ function hasLines(text: string) {
*/
function inlineComments(code: string): string {
const makeInlineComment = (comment: string) => {
const value = comment?.trim();
const value = comment.trim();
if (!value) {
return '<##>';
}

View File

@@ -15,12 +15,6 @@ export class PipeFactory implements IPipeFactory {
private readonly pipes = new Map<string, IPipe>();
constructor(pipes: readonly IPipe[] = RegisteredPipes) {
if (!pipes) {
throw new Error('missing pipes');
}
if (pipes.some((pipe) => !pipe)) {
throw new Error('missing pipe in list');
}
for (const pipe of pipes) {
this.registerPipe(pipe);
}
@@ -28,10 +22,11 @@ export class PipeFactory implements IPipeFactory {
public get(pipeName: string): IPipe {
validatePipeName(pipeName);
if (!this.pipes.has(pipeName)) {
const pipe = this.pipes.get(pipeName);
if (!pipe) {
throw new Error(`Unknown pipe: "${pipeName}"`);
}
return this.pipes.get(pipeName);
return pipe;
}
private registerPipe(pipe: IPipe): void {

View File

@@ -5,6 +5,7 @@ import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function
import { IExpression } from '../Expression/IExpression';
import { ExpressionPosition } from '../Expression/ExpressionPosition';
import { ExpressionRegexBuilder } from '../Parser/Regex/ExpressionRegexBuilder';
import { createPositionFromRegexFullMatch } from '../Expression/ExpressionPositionFactory';
export class WithParser implements IExpressionParser {
public findExpressions(code: string): IExpression[] {
@@ -42,31 +43,25 @@ function parseAllWithExpressions(
expressions.push({
type: WithStatementType.Start,
parameterName: match[1],
position: createPosition(match),
position: createPositionFromRegexFullMatch(match),
});
}
for (const match of input.matchAll(WithStatementEndRegEx)) {
expressions.push({
type: WithStatementType.End,
position: createPosition(match),
position: createPositionFromRegexFullMatch(match),
});
}
for (const match of input.matchAll(ContextVariableWithPipelineRegEx)) {
expressions.push({
type: WithStatementType.ContextVariable,
position: createPosition(match),
position: createPositionFromRegexFullMatch(match),
pipeline: match[1],
});
}
return expressions;
}
function createPosition(match: RegExpMatchArray): ExpressionPosition {
const startPos = match.index;
const endPos = startPos + match[0].length;
return new ExpressionPosition(startPos, endPos);
}
class WithStatementBuilder {
private readonly contextVariables = new Array<{
readonly positionInScope: ExpressionPosition;
@@ -125,7 +120,7 @@ class WithStatementBuilder {
private substituteContextVariables(
scope: string,
substituter: (pipeline: string) => string,
substituter: (pipeline?: string) => string,
): string {
if (!this.contextVariables.length) {
return scope;
@@ -157,7 +152,7 @@ function parseWithExpressions(input: string): IExpression[] {
.sort((a, b) => b.position.start - a.position.start);
const expressions = new Array<IExpression>();
const builders = new Array<WithStatementBuilder>();
const throwWithContext = (message: string) => {
const throwWithContext = (message: string): never => {
throw new Error(`${message}\n${buildErrorContext(input, allStatements)}}`);
};
while (sortedStatements.length > 0) {
@@ -178,12 +173,15 @@ function parseWithExpressions(input: string): IExpression[] {
}
builders[builders.length - 1].addContextVariable(statement.position, statement.pipeline);
break;
case WithStatementType.End:
if (builders.length === 0) {
case WithStatementType.End: {
const builder = builders.pop();
if (!builder) {
throwWithContext('Redundant `end` statement, missing `with`?');
break;
}
expressions.push(builders.pop().buildExpression(statement.position, input));
expressions.push(builder.buildExpression(statement.position, input));
break;
}
}
}
if (builders.length > 0) {