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,9 +1,15 @@
import type { ReadOnlyTreeNode } from '../Node/TreeNode';
export interface TreeViewFilterEvent {
readonly action: TreeViewFilterAction;
readonly predicate?: TreeViewFilterPredicate;
}
type TreeViewFilterTriggeredEvent = {
readonly action: TreeViewFilterAction.Triggered;
readonly predicate: TreeViewFilterPredicate;
};
type TreeViewFilterRemovedEvent = {
readonly action: TreeViewFilterAction.Removed;
};
export type TreeViewFilterEvent = TreeViewFilterTriggeredEvent | TreeViewFilterRemovedEvent;
export enum TreeViewFilterAction {
Triggered,
@@ -14,14 +20,14 @@ export type TreeViewFilterPredicate = (node: ReadOnlyTreeNode) => boolean;
export function createFilterTriggeredEvent(
predicate: TreeViewFilterPredicate,
): TreeViewFilterEvent {
): TreeViewFilterTriggeredEvent {
return {
action: TreeViewFilterAction.Triggered,
predicate,
};
}
export function createFilterRemovedEvent(): TreeViewFilterEvent {
export function createFilterRemovedEvent(): TreeViewFilterRemovedEvent {
return {
action: TreeViewFilterAction.Removed,
};

View File

@@ -25,7 +25,7 @@ export class TreeNodeHierarchy implements HierarchyAccess {
this.children = children;
}
public setParent(parent: TreeNode): void {
public setParent(parent: TreeNode | undefined): void {
this.parent = parent;
}
}

View File

@@ -5,9 +5,6 @@ import { TreeNodeManager } from '../../Node/TreeNodeManager';
export function parseTreeInput(
input: readonly TreeInputNodeData[],
): TreeNode[] {
if (!input) {
throw new Error('missing input');
}
if (!Array.isArray(input)) {
throw new Error('input data must be an array');
}

View File

@@ -11,7 +11,7 @@ export class TreeNodeInitializerAndUpdater implements TreeNodeCollection {
public nodesUpdated = new EventSource<QueryableNodes>();
public updateRootNodes(rootNodesData: readonly TreeInputNodeData[]): void {
if (!rootNodesData?.length) {
if (!rootNodesData.length) {
throw new Error('missing data');
}
const rootNodes = this.treeNodeParser(rootNodesData);

View File

@@ -71,8 +71,12 @@ TreeNavigationKeyCodes,
};
function focusPreviousVisibleNode(context: TreeNavigationContext): void {
const focusedNode = context.focus.currentSingleFocusedNode;
if (!focusedNode) {
return;
}
const previousVisibleNode = findPreviousVisibleNode(
context.focus.currentSingleFocusedNode,
focusedNode,
context.nodes,
);
if (!previousVisibleNode) {
@@ -83,6 +87,9 @@ function focusPreviousVisibleNode(context: TreeNavigationContext): void {
function focusNextVisibleNode(context: TreeNavigationContext): void {
const focusedNode = context.focus.currentSingleFocusedNode;
if (!focusedNode) {
return;
}
const nextVisibleNode = findNextVisibleNode(focusedNode, context.nodes);
if (!nextVisibleNode) {
return;
@@ -92,6 +99,9 @@ function focusNextVisibleNode(context: TreeNavigationContext): void {
function toggleTreeNodeCheckStatus(context: TreeNavigationContext): void {
const focusedNode = context.focus.currentSingleFocusedNode;
if (!focusedNode) {
return;
}
const nodeState = focusedNode.state;
let transaction = nodeState.beginTransaction();
if (nodeState.current.checkState === TreeNodeCheckState.Checked) {
@@ -104,19 +114,28 @@ function toggleTreeNodeCheckStatus(context: TreeNavigationContext): void {
function collapseNodeOrFocusParent(context: TreeNavigationContext): void {
const focusedNode = context.focus.currentSingleFocusedNode;
if (!focusedNode) {
return;
}
const nodeState = focusedNode.state;
const parentNode = focusedNode.hierarchy.parent;
if (focusedNode.hierarchy.isBranchNode && nodeState.current.isExpanded) {
nodeState.commitTransaction(
nodeState.beginTransaction().withExpansionState(false),
);
} else {
const parentNode = focusedNode.hierarchy.parent;
if (!parentNode) {
return;
}
context.focus.setSingleFocus(parentNode);
}
}
function expandNodeOrFocusFirstChild(context: TreeNavigationContext): void {
const focusedNode = context.focus.currentSingleFocusedNode;
if (!focusedNode) {
return;
}
const nodeState = focusedNode.state;
if (focusedNode.hierarchy.isBranchNode && !nodeState.current.isExpanded) {
nodeState.commitTransaction(
@@ -151,7 +170,10 @@ function findNextNode(node: TreeNode, nodes: QueryableNodes): TreeNode | undefin
return nodes.flattenedNodes[index + 1] || undefined;
}
function findPreviousVisibleNode(node: TreeNode, nodes: QueryableNodes): TreeNode | undefined {
function findPreviousVisibleNode(
node: TreeNode,
nodes: QueryableNodes,
): TreeNode | undefined {
const previousNode = findPreviousNode(node, nodes);
if (!previousNode) {
return node.hierarchy.parent;

View File

@@ -14,6 +14,7 @@ export function useTreeQueryFilter(
const { nodes } = useCurrentTreeNodes(treeRootRef);
let isFiltering = false;
const statesBeforeFiltering = new NodeStateRestorer();
statesBeforeFiltering.saveStateBeforeFilter(nodes.value);
@@ -35,7 +36,7 @@ export function useTreeQueryFilter(
currentNodes.flattenedNodes.forEach((node: TreeNode) => {
let transaction = node.state.beginTransaction()
.withMatchState(false);
transaction = statesBeforeFiltering.applyOriginalState(node, transaction);
transaction = statesBeforeFiltering.applyStateBeforeFilter(node, transaction);
node.state.commitTransaction(transaction);
});
statesBeforeFiltering.clear();
@@ -150,18 +151,20 @@ class NodeStateRestorer {
});
}
public applyOriginalState(
public applyStateBeforeFilter(
node: TreeNode,
transaction: TreeNodeStateTransaction,
): TreeNodeStateTransaction {
if (!this.originalStates.has(node)) {
const originalState = this.originalStates.get(node);
if (!originalState) {
return transaction;
}
const originalState = this.originalStates.get(node);
if (originalState.isExpanded !== undefined) {
transaction = transaction.withExpansionState(originalState.isExpanded);
}
transaction = transaction.withVisibilityState(originalState.isVisible);
if (originalState.isVisible !== undefined) {
transaction = transaction.withVisibilityState(originalState.isVisible);
}
return transaction;
}
@@ -185,18 +188,13 @@ function setupWatchers(options: {
options.nodesRef,
],
([filterEvent, nodes]) => {
if (!filterEvent) {
if (filterEvent === undefined) {
return;
}
switch (filterEvent.action) {
case TreeViewFilterAction.Triggered:
options.onFilterTrigger(filterEvent.predicate, nodes);
break;
case TreeViewFilterAction.Removed:
options.onFilterReset();
break;
default:
throw new Error(`Unknown action: ${TreeViewFilterAction[filterEvent.action]}`);
if (filterEvent.action === TreeViewFilterAction.Triggered) {
options.onFilterTrigger(filterEvent.predicate, nodes);
} else {
options.onFilterReset();
}
},
{ immediate: true },