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:
@@ -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);
|
||||
|
||||
@@ -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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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[] {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 '<##>';
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user