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

@@ -1,28 +1,84 @@
import { it } from 'vitest';
export function itEachAbsentStringValue(
runner: (absentValue: string | null) => void,
options: { excludeUndefined: true, excludeNull?: false }
): void;
export function itEachAbsentStringValue(
runner: (absentValue: string | undefined) => void,
options: { excludeUndefined?: false, excludeNull: true }
): void;
export function itEachAbsentStringValue(
runner: (absentValue: string) => void,
options: IAbsentTestCaseOptions = DefaultAbsentTestCaseOptions,
options: { excludeUndefined: true, excludeNull: true }
): void;
export function itEachAbsentStringValue(
runner: (absentValue: string | null | undefined) => void,
options?: { excludeUndefined?: false, excludeNull?: false },
): void;
export function itEachAbsentStringValue(
runner: ((absentValue: string) => void)
| ((absentValue: string | null) => void)
| ((absentValue: string | undefined) => void)
| ((absentValue: string | null | undefined) => void),
options: AbsentTestCaseOptions = DefaultAbsentTestCaseOptions,
): void {
itEachAbsentTestCase(getAbsentStringTestCases(options), runner);
// Using `as any` due to limitation of TypeScript, this may be fixed in future versions.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
itEachAbsentTestCase(getAbsentStringTestCases(options as any), runner);
}
export function itEachAbsentCollectionValue<T>(
runner: (absentValue: T[] | null) => void,
options: { excludeUndefined: true, excludeNull?: false }
): void;
export function itEachAbsentCollectionValue<T>(
runner: (absentValue: T[] | undefined) => void,
options: { excludeUndefined?: false, excludeNull: true }
): void;
export function itEachAbsentCollectionValue<T>(
runner: (absentValue: T[]) => void,
options: { excludeUndefined: true, excludeNull: true }
): void;
export function itEachAbsentCollectionValue<T>(
runner: (absentValue: T[] | null | undefined) => void,
options?: { excludeUndefined?: false, excludeNull?: false },
): void;
export function itEachAbsentCollectionValue<T>(
runner: ((absentValue: T[]) => void)
| ((absentValue: T[] | null) => void)
| ((absentValue: T[] | undefined) => void)
| ((absentValue: T[] | null | undefined) => void),
options: AbsentTestCaseOptions = DefaultAbsentTestCaseOptions,
): void {
// Using `as any` due to limitation of TypeScript, this may be fixed in future versions.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
itEachAbsentTestCase(getAbsentCollectionTestCases<T>(options as any), runner);
}
export function itEachAbsentObjectValue(
runner: (absentValue: AbsentObjectType) => void,
options: IAbsentTestCaseOptions = DefaultAbsentTestCaseOptions,
runner: (absentValue: null) => void,
options: { excludeUndefined: true, excludeNull?: false },
): void;
export function itEachAbsentObjectValue(
runner: (absentValue: undefined) => void,
options: { excludeUndefined?: false, excludeNull: true },
): void;
export function itEachAbsentObjectValue(
runner: (absentValue: undefined | null) => void,
options?: { excludeUndefined?: false, excludeNull?: false } | AbsentTestCaseOptions,
): void;
export function itEachAbsentObjectValue(
runner: ((absentValue: null) => void)
| ((absentValue: undefined) => void)
| ((absentValue: null | undefined) => void),
options: AbsentTestCaseOptions = DefaultAbsentTestCaseOptions,
): void {
itEachAbsentTestCase(getAbsentObjectTestCases(options), runner);
}
export function itEachAbsentCollectionValue<T>(
runner: (absentValue: []) => void,
options: IAbsentTestCaseOptions = DefaultAbsentTestCaseOptions,
): void {
itEachAbsentTestCase(getAbsentCollectionTestCases<T>(options), runner);
}
export function itEachAbsentTestCase<T>(
testCases: readonly IAbsentTestCase<T>[],
testCases: readonly AbsentTestCase<T>[],
runner: (absentValue: T) => void,
): void {
for (const testCase of testCases) {
@@ -32,68 +88,106 @@ export function itEachAbsentTestCase<T>(
}
}
export function getAbsentObjectTestCases(
options: IAbsentTestCaseOptions = DefaultAbsentTestCaseOptions,
): IAbsentTestCase<AbsentObjectType>[] {
return [
{
valueName: 'null',
absentValue: null,
},
...(options.excludeUndefined ? [] : [
{
valueName: 'undefined',
absentValue: undefined,
},
]),
];
interface AbsentTestCaseOptions {
readonly excludeUndefined?: boolean;
readonly excludeNull?: boolean;
}
export function getAbsentStringTestCases(
options: IAbsentTestCaseOptions = DefaultAbsentTestCaseOptions,
): IAbsentStringCase[] {
return [
{
valueName: 'empty',
absentValue: '',
},
...getAbsentObjectTestCases(options),
];
}
export function getAbsentCollectionTestCases<T>(
options: IAbsentTestCaseOptions = DefaultAbsentTestCaseOptions,
): readonly IAbsentCollectionCase<T>[] {
return [
...getAbsentObjectTestCases(options),
{
valueName: 'empty',
absentValue: new Array<T>(),
},
];
}
const DefaultAbsentTestCaseOptions: IAbsentTestCaseOptions = {
const DefaultAbsentTestCaseOptions: AbsentTestCaseOptions = {
excludeUndefined: false,
excludeNull: false,
};
interface IAbsentTestCaseOptions {
readonly excludeUndefined: boolean;
}
type AbsentObjectType = undefined | null;
interface IAbsentTestCase<T> {
interface AbsentTestCase<T> {
readonly valueName: string;
readonly absentValue: T;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface IAbsentStringCase extends IAbsentTestCase<string> {
// Marker interface
export function getAbsentObjectTestCases(
options: { excludeUndefined: true, excludeNull?: false },
): ReadonlyArray<AbsentTestCase<null>>;
export function getAbsentObjectTestCases(
options: { excludeUndefined?: false, excludeNull: true },
): ReadonlyArray<AbsentTestCase<undefined>>;
export function getAbsentObjectTestCases(
options: { excludeUndefined: true, excludeNull: true },
): ReadonlyArray<never>;
export function getAbsentObjectTestCases(
options?: { excludeUndefined?: false, excludeNull?: false } | AbsentTestCaseOptions,
): ReadonlyArray<AbsentTestCase<null> | AbsentTestCase<undefined>>;
export function getAbsentObjectTestCases(
options: AbsentTestCaseOptions = DefaultAbsentTestCaseOptions,
): ReadonlyArray<AbsentTestCase<null> | AbsentTestCase<undefined>> {
const results: Array<AbsentTestCase<null> | AbsentTestCase<undefined>> = [];
if (!options.excludeNull) {
results.push({
valueName: 'null',
absentValue: null,
});
}
if (!options.excludeUndefined) {
results.push({
valueName: 'undefined',
absentValue: undefined,
});
}
return results;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface IAbsentCollectionCase<T> extends IAbsentTestCase<T[]> {
// Marker interface
export function getAbsentStringTestCases(
options?: { excludeUndefined: false, excludeNull: false },
): ReadonlyArray<AbsentTestCase<string> | AbsentTestCase<null> | AbsentTestCase<undefined>>;
export function getAbsentStringTestCases(
options: { excludeUndefined: true, excludeNull?: false }
): ReadonlyArray<AbsentTestCase<string> | AbsentTestCase<null>>;
export function getAbsentStringTestCases(
options: { excludeUndefined?: false, excludeNull: true }
): ReadonlyArray<AbsentTestCase<string> | AbsentTestCase<undefined>>;
export function getAbsentStringTestCases(
options: { excludeUndefined: true, excludeNull: true }
): ReadonlyArray<AbsentTestCase<string>>;
export function getAbsentStringTestCases(
options: AbsentTestCaseOptions = DefaultAbsentTestCaseOptions,
): ReadonlyArray<AbsentTestCase<string> | AbsentTestCase<null> | AbsentTestCase<undefined>> {
const results: Array<(
AbsentTestCase<string> | AbsentTestCase<null> | AbsentTestCase<undefined>
)> = [];
results.push({
valueName: 'empty',
absentValue: '',
});
const objectTestCases = getAbsentObjectTestCases(options);
results.push(...objectTestCases);
return results;
}
export function getAbsentCollectionTestCases<T>(
options?: { excludeUndefined: false, excludeNull: false },
): ReadonlyArray<AbsentTestCase<T[]> | AbsentTestCase<null> | AbsentTestCase<undefined>>;
export function getAbsentCollectionTestCases<T>(
options: { excludeUndefined: true, excludeNull?: false }
): ReadonlyArray<AbsentTestCase<T[]> | AbsentTestCase<null>>;
export function getAbsentCollectionTestCases<T>(
options: { excludeUndefined?: false, excludeNull: true }
): ReadonlyArray<AbsentTestCase<T[]> | AbsentTestCase<undefined>>;
export function getAbsentCollectionTestCases<T>(
options: { excludeUndefined: true, excludeNull: true }
): ReadonlyArray<AbsentTestCase<T[]>>;
export function getAbsentCollectionTestCases<T>(
options: AbsentTestCaseOptions = DefaultAbsentTestCaseOptions,
): ReadonlyArray<AbsentTestCase<T[]> | AbsentTestCase<null> | AbsentTestCase<undefined>> {
const results: Array<AbsentTestCase<T[]> | AbsentTestCase<null> | AbsentTestCase<undefined>> = [];
const objectTestCases = getAbsentObjectTestCases(options);
results.push(...objectTestCases);
results.push({
valueName: 'empty',
absentValue: [],
});
return results;
}