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

@@ -9,25 +9,13 @@ import { ExpressionEvaluationContextStub } from '@tests/unit/shared/Stubs/Expres
import { IPipelineCompiler } from '@/application/Parser/Script/Compiler/Expressions/Pipes/IPipelineCompiler';
import { PipelineCompilerStub } from '@tests/unit/shared/Stubs/PipelineCompilerStub';
import { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
import { getAbsentObjectTestCases, itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
import { IExpressionEvaluationContext } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext';
import { expectExists } from '@tests/shared/Assertions/ExpectExists';
describe('Expression', () => {
describe('ctor', () => {
describe('position', () => {
describe('throws when missing', () => {
itEachAbsentObjectValue((absentValue) => {
// arrange
const expectedError = 'missing position';
const position = absentValue;
// act
const act = () => new ExpressionBuilder()
.withPosition(position)
.build();
// assert
expect(act).to.throw(expectedError);
});
});
it('sets as expected', () => {
// arrange
const expected = new ExpressionPosition(0, 5);
@@ -52,7 +40,7 @@ describe('Expression', () => {
expect(actual.parameters);
expect(actual.parameters.all);
expect(actual.parameters.all.length).to.equal(0);
});
}, { excludeNull: true });
});
it('sets as expected', () => {
// arrange
@@ -67,21 +55,6 @@ describe('Expression', () => {
expect(actual.parameters).to.deep.equal(expected);
});
});
describe('evaluator', () => {
describe('throws if missing', () => {
itEachAbsentObjectValue((absentValue) => {
// arrange
const expectedError = 'missing evaluator';
const evaluator = absentValue;
// act
const act = () => new ExpressionBuilder()
.withEvaluator(evaluator)
.build();
// assert
expect(act).to.throw(expectedError);
});
});
});
});
describe('evaluate', () => {
describe('throws with invalid arguments', () => {
@@ -91,11 +64,6 @@ describe('Expression', () => {
expectedError: string,
sutBuilder?: (builder: ExpressionBuilder) => ExpressionBuilder,
}[] = [
...getAbsentObjectTestCases().map((testCase) => ({
name: `throws if arguments is ${testCase.valueName}`,
context: testCase.absentValue,
expectedError: 'missing context',
})),
{
name: 'throws when some of the required args are not provided',
sutBuilder: (i: ExpressionBuilder) => i.withParameterNames(['a', 'b', 'c'], false),
@@ -159,7 +127,7 @@ describe('Expression', () => {
const expected = new PipelineCompilerStub();
const context = new ExpressionEvaluationContextStub()
.withPipelineCompiler(expected);
let actual: IPipelineCompiler;
let actual: IPipelineCompiler | undefined;
const evaluatorMock: ExpressionEvaluator = (c) => {
actual = c.pipelineCompiler;
return '';
@@ -170,6 +138,7 @@ describe('Expression', () => {
// arrange
sut.evaluate(context);
// assert
expectExists(actual);
expect(expected).to.equal(actual);
});
describe('filters unused parameters', () => {
@@ -202,7 +171,7 @@ describe('Expression', () => {
];
for (const testCase of testCases) {
it(testCase.name, () => {
let actual: IReadOnlyFunctionCallArgumentCollection;
let actual: IReadOnlyFunctionCallArgumentCollection | undefined;
const evaluatorMock: ExpressionEvaluator = (c) => {
actual = c.args;
return '';
@@ -216,8 +185,10 @@ describe('Expression', () => {
// act
sut.evaluate(context);
// assert
const actualArguments = actual.getAllParameterNames()
.map((name) => actual.getArgument(name));
expectExists(actual);
const collection = actual;
const actualArguments = collection.getAllParameterNames()
.map((name) => collection.getArgument(name));
expect(actualArguments).to.deep.equal(testCase.expectedArguments);
});
}
@@ -228,7 +199,7 @@ describe('Expression', () => {
class ExpressionBuilder {
private position: ExpressionPosition = new ExpressionPosition(0, 5);
private parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub();
private parameters?: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub();
public withPosition(position: ExpressionPosition) {
this.position = position;
@@ -240,7 +211,7 @@ class ExpressionBuilder {
return this;
}
public withParameters(parameters: IReadOnlyFunctionParameterCollection) {
public withParameters(parameters: IReadOnlyFunctionParameterCollection | undefined) {
this.parameters = parameters;
return this;
}
@@ -261,5 +232,5 @@ class ExpressionBuilder {
return new Expression(this.position, this.evaluator, this.parameters);
}
private evaluator: ExpressionEvaluator = () => '';
private evaluator: ExpressionEvaluator = () => `[${ExpressionBuilder.name}] evaluated-expression`;
}